Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Has Many Through Polymorphic Checkboxes

This one's really getting me down! :(

I'm trying to make a nested model form for my User model with a checkbox list in it where multiple Stores can be checked or unchecked to administer the Stores through model Staffing.

class Staffing < ActiveRecord::Base
  # Associations
  belongs_to :user
  belongs_to :staffable, :polymorphic => true
  belongs_to :store,     :class_name => "Store",
                         :foreign_key => "staffable_id"
end

class User < ActiveRecord::Base
  # Includes
  acts_as_authentic

  # Associations
  has_many :staffings, :dependent => :destroy
  has_many :stores, :through => :staffings

  # Nested Attributes
  accepts_nested_attributes_for :staffings, :allow_destroy => true
end

class Store < ActiveRecord::Base
  # Associations
  has_many :staffings, :as => :staffable, :dependent => :destroy
  has_many :users, :through => :staffings
end


# Users Controller
def create
  @user = User.new(params[:user])
  flash.now[:notice] = "#{@user.name} was successfully created." if @user.save
  respond_with @user
end


def update
  @user = User.find(params[:id])
  params[:user][:store_ids] ||= []
  @user.update_attributes(params[:user])
  flash.now[:notice] = "#{@user.name} was successfully updated."
  respond_with @user
end

For the purposes at hand the other staffable association can be ignored. It's a similar model that I'd eventually want to administer alongside Store but first thing's first since I'm so stumped as it is.

# User Form
- for store in Store.all
  %p
    = check_box_tag "user[store_ids][]", store.id, @user.stores.include?(store)
    = store.nickname

Sends my params along as such:

{"utf8"=>"✓",
 "authenticity_token"=>"blub-blub-blub=",
 "user"=>{"login"=>"hey.buddy",
 "email"=>"[email protected]",
 "role"=>"admin",
 "hq"=>"1",
 "password"=>"[FILTERED]",
 "password_confirmation"=>"[FILTERED]",
 "store_ids"=>["3",
 "4"]}}

And then ERROR!

Mysql2::Error: Column 'staffable_type' cannot be null: INSERT INTO `staffings` (`created_at`, `staffable_id`, `staffable_type`, `updated_at`, `user_id`) VALUES ('2010-11-17 00:30:24', 3, NULL, '2010-11-17 00:30:24', 102)

I know I have to build out staffable_type as 'Store' but I've been trying all day--what's the trick?

I do have the staffable_type (and id) columns set to :null => false but that can't be the cause of this as this needs to be straightened out in the controller before hitting the DB anyway.

Why does the below not work in my create action:

@user.staffings.each do |s|
  s.staffable_type = 'Store'
end

or:

@user.store_ids.each do |i|
  s = @user.staffings.build
  s.staffable_type = 'Store'
  s.staffable_id = i
end

If been trying many things similar to the above to no avail. Any help would be massively appreciated.

Thanks for your time!

like image 850
blastula Avatar asked Oct 14 '22 21:10

blastula


1 Answers

Okay I figured this one out. I'm answering here so I can hopefully help someone someday, but the problem was a basic oversight on the difference between Has Many Through and Has And Belongs To Many Associations.

You if you want to handle checkboxes or multiselect lists in a Has Many Through, there is no '_ids' method generated for you by the association and you need to write one for yourself.

See Paul Barry's article here: http://paulbarry.com/articles/2007/10/24/has_many-through-checkboxes

Polymorphic stuff can be complicated, but if you're stuck real good maybe take a step back and make sure it's not something even more basic that's messing you up-- worked for me!

Below is the code modified from Paul's blog on how to handle this if you're dealing with a polymorphic has many through (basically you just need to use the attributes hash and write your polymorphic_type attribute):

attr_accessor :store_ids
after_save :update_stores

#after_save callback to handle group_ids
def update_stores
  unless store_ids.nil?
    self.staffings.each do |staffing|
      staffing.destroy unless store_ids.include?(staffing.staffable_id.to_s)
      store_ids.delete(staffing.staffable_id.to_s)
    end 
    store_ids.each do |s|
      self.staffings.create(attributes = {:staffable_id => s, :staffable_type => 'Store'}) unless s.blank?
    end
    reload
    self.store_ids = nil
  end
end 
like image 83
blastula Avatar answered Oct 27 '22 10:10

blastula