Strange has_many Association Behavior in Rails 4

67 Views Asked by At

I've got two tables, User and Allergy. These are connected via another table, UserAllergy. The models are as would be expected:

class User
  has_many :user_allergies
  has_many :allergies, through: :user_allergies
end

class UserAllergy
  belongs_to :user
  belongs_to :allergy
end

class Allergy
  has_many :user_allergies
  has_many :users, through :user_allergies
end

What I'm confused about is creating allergies from a multiple-valued collection_select in my User form.

I have the following field:

<%= f.collection_select :allergy_ids, 
                        Allergy.all, 
                        :id, 
                        :name, 
                        {}, 
                        { class: 'form-control', multiple: true }
%>

This correctly inserts a key into my params like so if I selected the Allergies with ids 1 and 2:

{ user: { id: "1", allergy_ids: ["", "1", "2"] } }

When I create the user instantiated with @user = User.new( my_params ), the weird behavior occurs. Instead of inserting the provided allergy_ids into the join table, Rails does a query to get all current user_allergies for the user, then deletes all of the current user_allergies:

Started PATCH "/employees/regular_user" for 127.0.0.1 at 2015-06-18 22:08:30 -0400
Processing by UsersController#update as HTML
  Parameters: {"utf8"=>"✓", "user"=>{ "allergy_ids"=>["", "1", "2", "3"]}, "button"=>"", "id"=>"regular_user"}
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
   (0.1ms)  begin transaction
  Allergy Load (0.1ms)  SELECT "allergies".* FROM "allergies" INNER JOIN "user_allergies" ON "allergies"."id" = "user_allergies"."allergy_id" WHERE "user_allergies"."user_id" = ?  [["user_id", 1]]
  SQL (0.1ms)  DELETE FROM "user_allergies" WHERE "user_allergies"."user_id" = ? AND "user_allergies"."allergy_id" = 1  [["user_id", 1]]
   (27.4ms)  commit transaction
Redirected to http://localhost:3000/employees/regular_user
Completed 302 Found in 32ms (ActiveRecord: 27.8ms)

Anyone knows what gives, or what I need to do to create allergies implicitly? I've tried accepts_nested_attributes_for and changing around the form to use fields_for.

3

There are 3 best solutions below

1
On BEST ANSWER

So, I went and looked at code of mine that does a similar function. Here's what my create method looks like. This is creating a Student with assignment to Student Groups in a school setting (I didn't use "class" since Ruby wouldn't like that).

def create
  @student = Student.new(student_params)

  if @student.save
    @student.student_groups = StudentGroup.where(id: params[:student][:student_group_ids])
    flash[:success] = "Student was successfully created."
    redirect_to @student
  else
    render 'new', notice: "Your student could not be created."
  end
end

I completely ignore the Student Group IDs when creating the student_params, since I'm not using them for mass assignment.

Yes, one extra line of code. I'd be really interested to hear if there's a way to accomplish this via mass assignment.

1
On

You're missing one part of the puzzle which is the relation from Allergy to User.

class Allergy
  has_many :user_allergies
  has_many :users, through: :user_allergies
end
0
On

Just give the following code a try-

params.require(:user).permit(___, ____, {allergy_ids: []}, ____, ____)