108
class Agents << ActiveRecord::Base
  belongs_to :customer
  belongs_to :house
end

class Customer << ActiveRecord::Base
  has_many :agents
  has_many :houses, through: :agents
end

class House << ActiveRecord::Base
  has_many :agents
  has_many :customers, through: :agents
end

How do I add to the Agents model for Customer?

Is this the best way?

Customer.find(1).agents.create(customer_id: 1, house_id: 1)

The above works fine from the console however, I don't know how to achieve this in the actual application.

Imagine a form is filled for the customer that also takes house_id as input. Then do I do the following in my controller?

def create 
  @customer = Customer.new(params[:customer])
  @customer.agents.create(customer_id: @customer.id, house_id: params[:house_id])
  @customer.save
end

Overall I'm confused as to how to add records in the has_many :through table?

davegson
  • 8,205
  • 4
  • 51
  • 71
Mike
  • 1,361
  • 2
  • 13
  • 13

3 Answers3

176

I think you can simply do this:

 @cust = Customer.new(params[:customer])
 @cust.houses << House.find(params[:house_id])

Or when creating a new house for a customer:

 @cust = Customer.new(params[:customer])
 @cust.houses.create(params[:house])

You can also add via ids:

@cust.house_ids << House.find(params[:house_id])
BenKoshy
  • 33,477
  • 14
  • 111
  • 80
Mischa
  • 42,876
  • 8
  • 99
  • 111
  • 19
    FYI: You can't create the associated house unless the parent is already saved. – rikas Dec 16 '14 at 18:14
  • That has to be the most elegant solution to this problem I've come across. +1 for you. – Daniel Bonnell Feb 09 '16 at 17:27
  • @RicardoOtero I guess we can use `build` istead of `create`? – Karan Jun 01 '16 at 09:52
  • @Mischa how should i handle error if House.find(params[:house_id]) is nill.. i got error of TypeMismatch if params[:house_id] is nil.. i already using rescue. but is there any better_way..?? – Vishal Jul 30 '16 at 10:51
  • 1
    I have observed that using `<<` operator does insertion twice in certain cases. So the `create` method is the best way. – Swaps Feb 20 '17 at 06:28
  • @Mischa, How to add multiple house details to particular customer at once, above answer shows it for one house pointing one customer. – pbms Dec 14 '17 at 08:05
84

Preface

This is a strange scenario and I hesitated to answer. It seems like Agents should have many Houses rather than a one-to-one relationship, and a House should belong to just one Agent. But with that in mind....

"The best way" depends on your needs and what feels most comfortable/readable to you. Confusion comes from differences in ActiveRecord's behavior of the new and create methods and the << operator, but they can all be used to accomplish your goal.

The new Method

new will not add an association record for you. You have to build the House and Agent records yourself:

# ...
house = @cust.houses.new(params[:house])
house.save
agent = Agent.new(customer: @cust house: house)
agent.save

Note that @cust.houses.new and House.new are effectively the same because you still need to create the Agent record in both cases.

(This code looks weird, you can't easily tell what it's supposed to be doing, and that's a smell that maybe the relationships are set up wrong.)

The << Operator

As Mischa mentions, you can also use the << operator on the collection. This will only build the Agent model for you, you must build the House model:

house = House.create(params[:house])
@cust.houses << house
agent = @cust.houses.find(house.id)

The create Method

create will build both House and Agent records for you, but you will need to find the Agent model if you intend to return that to your view or api:

house = @cust.houses.create(params[:house])
agent = @cust.agents.where(house: house.id).first

As a final note, if you want exceptions to be raised when creating house use the bang operators instead (e.g. new! and create!).

IAmNaN
  • 10,305
  • 3
  • 53
  • 51
  • 2
    Should the line `agent = @cust.houses.find(house.id)` read `agent = @cust.agents.find(house.id)` instead? The `agent` variable in the "new Method" is different to the `agent` in the latter examples. Might create some confusion for people working with additional attributes on the join table. – vaughan Sep 03 '13 at 06:51
  • can you elaborate on retrieving data from the joint table Agents without having N+1 bug example displaying all the houses and corresponding agents for the given customer – Ankita.P Jul 24 '15 at 11:00
6

Another way to add associations is by using the foreign key columns:

agent = Agent.new(...)
agent.house = House.find(...)
agent.customer = Customer.find(...)
agent.save

Or use the exact column names, passing the ID of the associated record instead of the record.

agent.house_id = house.id
agent.customer_id = customer.id
Dennis
  • 56,821
  • 26
  • 143
  • 139