Using has_many: through and build

I have three models, all for has_many: through relationships. They look like this:

class Company < ActiveRecord::Base has_many :company_users, dependent: :destroy has_many :users, through: :company_users accepts_nested_attributes_for :company_users, :users end class CompanyUser < ActiveRecord::Base self.table_name = :companies_users #this is because this was originally a habtm relationship belongs_to :company belongs_to :user end class User < ActiveRecord::Base # this is a devise model, if that matters has_many :company_users, dependent: :destroy has_many :companies, through: :company_users accepts_nested_attributes_for :company_users, :companies end 

it loads perfectly and connections are built perfectly for requests. However, when I do something like

 @company = Company.last @user = @company.users.build(params[:user]) @user.save #=> true @company.save #=> true 

User entries and CompanyUser entries are CompanyUser , but the company_id field in the CompanyUser entry CompanyUser set to NULL

 INSERT INTO `companies_users` (`company_id`, `created_at`,`updated_at`, `user_id`) VALUES (NULL, '2012-02-19 02:09:04', '2012-02-19 02:09:04', 18) 

he does the same when you @company.users << @user

I'm sure I'm doing something stupid here, I just don't know what.

+9
ruby ruby-on-rails has-many-through
source share
3 answers

You cannot use has_many: through , you must do it like this:

 @company = Company.last @user = User.create( params[:user] ) @company.company_users.create( :user_id => @user.id ) 

Then you define the association correctly.

Update

In the case of the comment below, since you already have accepts_nested_attributes_for , your options would have to look like this:

 { :company => { :company_users_attributes => [ { :company_id => 1, :user_id => 1 } , { :company_id => 1, :user_id => 2 }, { :company_id => 1, :user_id => 3 } ] } } 

And you will automatically add users to the company for you.

+14
source share

I suspect your params[:user] , otherwise your code seems to be clean. We can use build method with 1..n and n..n associations too , see here .

I suggest you first make sure that your model associations are working fine, to do this, open console and try the following,

 > company = Company.last => #<Tcompany id: 1....> > company.users => [] > company.users.build(:name => "Jake") => > #<User id: nil, name: "Jake"> > company.save => true 

Now, if the records are saved normally, debug the parameters that you pass to the build method.

Good debugging :)

+5
source share

If you have a has_many :through association and you want to keep the association using build you can do this using the :inverse_of option in the priv_to association in the connection model.

Here is a modified example from the rails document, in which tags have has_many: through communication with publications, and the developer tries to save the tags through the connection model (PostTag) using the build method:

 @post = Post.first @tag = @post.tags.build name: "ruby" @tag.save 

The general expectation is that the last line should save the end-to-end entry in the connection table (post_tags). However, this will not work by default. This will only work if set to: inverse_of :

 class PostTag < ActiveRecord::Base belongs_to :post belongs_to :tag, inverse_of: :post_tags # add inverse_of option end class Post < ActiveRecord::Base has_many :post_tags has_many :tags, through: :post_tags end class Tag < ActiveRecord::Base has_many :post_tags has_many :posts, through: :post_tags end 

Thus, for the question above, set the: inverse_of parameter for belongs_to :user in the connection model (CompanyUser) as follows:

 class CompanyUser < ActiveRecord::Base belongs_to :company belongs_to :user, inverse_of: :company_users end 

will cause the following code to correctly create an entry in the connection table (company_users)

 company = Company.first company.users.build(name: "James") company.save 

Source: here and here

+1
source share

All Articles