como adicionar registros a has_many: por meio de associação em trilhos

94
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

Como faço para adicionar ao Agentsmodelo para Customer?

É este o melhor caminho?

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

O procedimento acima funciona bem no console, porém, não sei como fazer isso no aplicativo real.

Imagine que um formulário é preenchido para o cliente e também recebe house_idcomo entrada. Então eu faço o seguinte no meu controlador?

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

No geral, estou confuso sobre como adicionar registros na has_many :throughtabela?

Mike
fonte
Em qual controlador você armazenaria a função "criar"?
Tobias Kolb,

Respostas:

162

Acho que você pode simplesmente fazer isso:

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

Ou ao criar uma nova casa para um cliente:

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

Você também pode adicionar via ids:

@cust.house_ids << House.find(params[:house_id])
Mischa
fonte
16
FYI: Você não pode criar a casa associada a menos que o pai já esteja salvo.
Ricardo Otero
Essa deve ser a solução mais elegante para o problema que encontrei. 1 para você.
Daniel Bonnell
@RicardoOtero Acho que podemos usar buildistead of create?
Karan
@Mischa como devo lidar com o erro se House.find (params [: house_id]) for nulo .. Eu obtive o erro de TypeMismatch se params [: house_id] for nulo .. Eu já estou usando o resgate. mas existe algum better_way .. ??
Vishal
1
Eu observei que usar o <<operador faz a inserção duas vezes em certos casos. Portanto, o createmétodo é o melhor caminho.
Troca de
77

'A melhor forma' depende das suas necessidades e do que lhe parece mais confortável. Confusão vem de diferenças comportamento de ActiveRecord do newe createmétodos eo <<operador.

O newmétodo

newnão adicionará um registro de associação para você. Você deve construir os registros Housee Agentvocê mesmo:

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

Observe que @cust.houses.newe House.newsão efetivamente os mesmos porque você precisa criar o Agentregistro em ambos os casos.

O <<operador

Como Mischa menciona, você também pode usar o <<operador na coleção. Isso só vai construir o Agentmodelo para você, você deve construir o Housemodelo:

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

O createmétodo

createcriará os registros Housee Agentpara você, mas você precisará encontrar o Agentmodelo se pretende retorná-lo à sua visualização ou api:

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

Como nota final, se você quiser que exceções sejam levantadas durante a criação, houseuse os operadores de explosão (por exemplo, new!e create!).

IAmNaN
fonte
2
A linha deveria ser agent = @cust.houses.find(house.id)lida em agent = @cust.agents.find(house.id)vez disso? A agentvariável no "novo método" é diferente da agentdos últimos exemplos. Pode criar alguma confusão para as pessoas que trabalham com atributos adicionais na tabela de junção.
vaughan,
você pode elaborar sobre como recuperar dados da tabela conjunta Agentes sem ter N + 1 exemplo de bug exibindo todas as casas e os agentes correspondentes para um determinado cliente
Ankita.P
6

Outra maneira de adicionar associações é usando as colunas de chave estrangeira:

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

Ou use os nomes exatos das colunas, passando o ID do registro associado em vez do registro.

agent.house_id = house.id
agent.customer_id = customer.id
Dennis
fonte