jak dodawać rekordy do has_many: poprzez asocjacje w szynach

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

Jak dodać do Agentsmodelu Customer?

Czy to najlepszy sposób?

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

Powyższe działa dobrze z konsoli, jednak nie wiem, jak to osiągnąć w rzeczywistej aplikacji.

Wyobraź sobie, że dla klienta wypełniany jest formularz, który również przyjmuje house_idjako dane wejściowe. Następnie mam wykonać następujące czynności na moim kontrolerze?

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

Ogólnie jestem zdezorientowany, jak dodawać rekordy w has_many :throughtabeli?

Mikrofon
źródło
W którym kontrolerze zapiszesz funkcję „create”?
Tobias Kolb

Odpowiedzi:

166

Myślę, że możesz po prostu zrobić to:

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

Lub tworząc nowy dom dla klienta:

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

Możesz również dodać za pomocą identyfikatorów:

@cust.house_ids << House.find(params[:house_id])
Mischa
źródło
16
Do Twojej wiadomości: Nie możesz utworzyć powiązanego domu, chyba że rodzic jest już zapisany.
Ricardo Otero
To chyba najbardziej eleganckie rozwiązanie tego problemu, z jakim się spotkałem. +1 dla Ciebie.
Daniel Bonnell
@RicardoOtero Myślę, że możemy użyć buildistead of create?
Karan
@Mischa, jak mam obsłużyć błąd, jeśli House.find (params [: house_id]) jest zerowy .. Otrzymałem błąd TypeMismatch, jeśli parametr [: house_id] ma wartość nil .. Już używam rescue. ale czy jest jakikolwiek Better_way .. ??
Vishal
1
Zauważyłem, że użycie <<operatora powoduje dwukrotne wstawienie w niektórych przypadkach. Więc createmetoda jest najlepsza.
Zamienia
78

„Najlepszy sposób” zależy od Twoich potrzeb i tego, co jest dla Ciebie najwygodniejsze. Zamieszanie wynika z różnic w zachowaniu ActiveRecord z neworaz createmetod i <<operatora.

newMetoda

newnie doda za Ciebie rekordu skojarzenia. Musisz sam zbudować Housei Agentnagrywać:

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

Zauważ, że @cust.houses.newi House.newfaktycznie są takie same, ponieważ musisz utworzyć Agentrekord w obu przypadkach.

<<Operator

Jak wspomina Mischa, możesz również użyć <<operatora na kolekcji. To zbuduje tylko Agentmodel dla Ciebie, musisz zbudować Housemodel:

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

createMetoda

createutworzy zarówno Housei Agentrekordy, jak i dla Ciebie, ale będziesz musiał znaleźć Agentmodel, jeśli zamierzasz przywrócić go do widoku lub interfejsu API:

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

Na koniec, jeśli chcesz, aby wyjątki były zgłaszane podczas tworzenia, houseużyj zamiast tego operatorów bang (np. new!I create!).

IAmNaN
źródło
2
Czy zamiast tego wiersz powinien agent = @cust.houses.find(house.id)brzmieć agent = @cust.agents.find(house.id)? agentZmienna w „Nowa metoda” różni się agentw dalszych przykładach. Może to spowodować zamieszanie dla osób pracujących z dodatkowymi atrybutami w tabeli łączenia.
vaughan,
czy możesz rozwinąć pobieranie danych ze wspólnej tabeli Agenci bez przykładu błędu N + 1 wyświetlającego wszystkie domy i odpowiadających im agentów dla danego klienta
Ankita.P
6

Innym sposobem dodawania asocjacji jest użycie kolumn klucza obcego:

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

Lub użyj dokładnych nazw kolumn, przekazując identyfikator skojarzonego rekordu zamiast rekordu.

agent.house_id = house.id
agent.customer_id = customer.id
Dennis
źródło