3

I can't figure out why it's generating duplicate recruit_profiles_skills instead of updating.

class RecruitProfile < ActiveRecord::Base
   has_many :skills, :through => :recruit_profiles_skills
   has_many :recruit_profiles_skills, :dependent => :destroy
   accepts_nested_attributes_for :recruit_profiles_skills, :allow_destroy => true

class Skill < ActiveRecord::Base
    has_many :recruit_profiles, :through => :recruit_profiles_skills
    has_many :recruit_profiles_skills, :dependent => :destroy

Params looks like

"recruit_profile"=>{
    "recruit_profiles_skills_attributes"=>[{"skill_id"=>"1", "level"=>"15"}]
}

Then I do

def update
    @recruit_profile.update_attributes(params[:recruit_profile])

But, this creates duplicate association records. Why does this not simply update!? I can prevent the duplicates using validations, but then it never updates since it just wants to create a new record, but the new record is invalid because it fails the validation.

hrdwdmrbl
  • 4,814
  • 2
  • 32
  • 41
  • I updated the question. It is the association record that I'm getting duplicates of. – hrdwdmrbl Nov 11 '11 at 20:20
  • 1
    Can you try passing in the `:id` of the `RecruitProfilesSkills` for the `recruit_profile_skills_attributes` as a parameter? The documentation leads me to believe that it will create a new record if it doesn't contain the id in the attributes hash. – Vincent Agnello Nov 11 '11 at 21:20
  • That will allow for the updating of current association records, but what if I want to delete some, add some and change some all at once? I want to be able to do for has_many :through, as I can with habtm. With habtm, I can just update_atrb(skill_ids) and it will delete associations of skill_ids that are no longer present, and add new ones for skill_ids are are new. If I update using the same params as above and also add ":id =>", then it will just update existing records and add new ones, but will not delete ones that are not present. Am I making sense? – hrdwdmrbl Nov 11 '11 at 22:04
  • Maybe I'm asking too much from rails... I actually have a helper method to do it like I want, but I just assumed rails would behave for :through associations like it does for HABTM associations. – hrdwdmrbl Nov 11 '11 at 22:11
  • 1
    It makes sens what you want. The documation I am looking at is [here](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html). If you look at the `_destroy` section it describes what you want I think. – Vincent Agnello Nov 11 '11 at 22:26
  • Thanks! I see that I will need to add a "_destroy => {:id}", key to my params hash if I would like it to destroy one of the associations. That isn't exactly the behavior I was expecting, but I guess it will have to do. – hrdwdmrbl Nov 14 '11 at 16:30
  • Cool, at least it kinda works how you want it to. – Vincent Agnello Nov 14 '11 at 21:11
  • I posted my full solution as an answer. I still think it should operate like HABTM does, but nothing is perfect. – hrdwdmrbl Nov 18 '11 at 16:07

1 Answers1

3

The way I solved this problem in code was to:

  1. Include the 'id' of the row in the association table in every attribute that has already been created. This allows for the updating to work as expected.
  2. Use a checkbox on skill_id attribute. Thus, if the checkbox is not checked, the skill_id will not show up in the params hash. Then, I run this bit of code



params[:recruit_profile][:recruit_profiles_skills_attributes].map{ |rps| 
    if rps[:skill_id].nil? then rps[:_destroy] = 1 end 
}

That bit of code will check to see if the :skill_id is set. If it is not set, then the row needs to be deleted. The way to delete entries, even if :allow_destroy is set to true, is to append a ":_destroy => 1" key=>value to the hash. Thus, the :id will be present, and a :_destroy will be present, so update_attributes will delete it.

Doing the above will allow for create (:id not present, but :skill_id present), update (:id present and :skill_id present) and destroy (:id present, but :skill_id not present). IMHO, that isn't how it should work, but the job gets done with only 1 extra line of code (split in to 3 lines because of length).

(NOTE: replace skill_id with whatever other parameter is also in your association table. This round about way is only needed if you're using association tables with multiple attributes. Otherwise, the classic collection_ids = [#,#,#] still works with has_many :through associations.)

hrdwdmrbl
  • 4,814
  • 2
  • 32
  • 41