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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With