Create Record With "has_many_through" Association – Ruby on Rails

105 views Asked by At

I have a has_many_through association where Users have many Projects through ProjectUsers. Rails has some magic that allows updating this relationship with:

u = User.first
u.update(project_ids: [...])

Is there a clean way to do the same thing with create?

Running User.create(name: ..., project_ids: [...]) fails with Validation failed: Project users is invalid.

I suspect this is because Rails tries to create the ProjectUser record before creating the User record and has some built-in validation on join tables to validate that both sides of the join already exist. The ProjectUser model has no custom validation.

class ProjectUser < ApplicationRecord
  belongs_to :project
  belongs_to :user
end

Is there a simple way to get around this?

3

There are 3 answers

0
titan2gman On BEST ANSWER

Active Record supports automatic identification for most associations with standard names. However, Active Record will not automatically identify bi-directional associations that contain the :through or :foreign_key options. (You can check here)

So you have to define inverse_of explicitly.

class Project < ApplicationRecord
  has_many :project_users, foreign_key: :project_id, inverse_of: :project
  has_many :users, through: :project_users
end

class User < ApplicationRecord
  has_many :project_users, foreign_key: :user_id, inverse_of: :user
  has_many :projects, through: :project_users
end

class ProjectUser < ApplicationRecord
  belongs_to :project
  belongs_to :user
end

0
smathy On

Generally people use hmt (instead of has_and_belongs_to_many) because they have other fields that they're interested in populating in the join table, hence people usually create the triplet of model records using the join table, eg.

ProjectUser.create! the_extra_field: "Foo", user: User.create!(name: "Jim"), project: Project.create!(title: "Project X")

Obviously if you already have an existing User or Project then you'd use those instead of creating new ones.

0
sapiv71097 On

I believe that you need to use accepts_nested_attributes_for

class User < ApplicationRecord
  has_many :project_users
  has_many :projects, through: :project_users

  accepts_nested_attributes_for :projects
end

That must help you to create users with associated projects like you expected

User.create(name: 'John', project_ids: [...])