как добавить записи в has_many: через ассоциацию в рельсах

97
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

Как добавить в Agentsмодель для Customer?

Это лучший способ?

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

Вышеупомянутое отлично работает с консоли, однако я не знаю, как этого добиться в реальном приложении.

Представьте, что для клиента заполняется форма, которая также используется в house_idкачестве входных данных. Тогда что я должен делать в своем контроллере?

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

В целом я не понимаю, как добавлять записи в has_many :throughтаблицу?

Майк
источник
В каком контроллере вы бы сохранили функцию «создать»?
Тобиас Колб

Ответы:

166

Я думаю, вы можете просто сделать это:

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

Или при создании нового дома для заказчика:

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

Вы также можете добавить через идентификаторы:

@cust.house_ids << House.find(params[:house_id])
Миша
источник
16
К вашему сведению: вы не можете создать связанный дом, если родитель уже не сохранен.
Рикардо Отеро
Это должно быть самое элегантное решение этой проблемы, с которым я сталкивался. +1 для вас.
Дэниел Боннелл,
@RicardoOtero Думаю, мы можем использовать buildistead of create?
Karan
@Mischa, как мне обрабатывать ошибку, если House.find (params [: house_id]) равен нулю .. У меня ошибка TypeMismatch, если params [: house_id] равно нулю .. Я уже использую спасение. но есть ли способ лучше .. ??
Vishal
1
Я заметил, что <<в некоторых случаях оператор using выполняет вставку дважды. Так что createметод - лучший способ.
свопы
78

«Лучший способ» зависит от ваших потребностей и от того, что вам удобнее всего. Путаница происходит от различия поведения ActiveRecord портретируемого , newи createметодов и <<оператора.

newМетод

newне будет добавлять за вас запись ассоциации. Вы должны сами построить Houseи Agentзаписать:

house = @cust.houses.new(params[:house])
house.save
agent = Agent(customer_id: @cust.id, house_id: house.id)
agent.save

Обратите внимание, что @cust.houses.newи House.newфактически одно и то же, потому что вам нужно создать Agentзапись в обоих случаях.

<<Оператор

Как упоминает Миша, вы также можете использовать <<оператор в коллекции. Это только построит Agentмодель для вас, вы должны построить Houseмодель:

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

createМетод

createбудет строить как Houseи Agentзапись для вас, но вам нужно будет найти Agentмодель , если вы намерены вернуться , что на ваш взгляд или API:

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

В заключение, если вы хотите, чтобы при создании возникали исключения, houseиспользуйте вместо этого операторы взрыва (например, new!и create!).

IAmNaN
источник
2
Следует ли agent = @cust.houses.find(house.id)читать строку agent = @cust.agents.find(house.id)вместо этого? agentПеременной в «новом методе» отличается от agentв последних примерах. Может создать некоторую путаницу для людей, работающих с дополнительными атрибутами в соединительной таблице.
vaughan
Можете ли вы подробнее
6

Другой способ добавить ассоциации - использовать столбцы внешнего ключа:

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

Или используйте точные имена столбцов, передав идентификатор связанной записи вместо записи.

agent.house_id = house.id
agent.customer_id = customer.id
Деннис
источник