przynależy_to poprzez skojarzenia


141

Biorąc pod uwagę następujące skojarzenia, muszę odnieść się do tego, Questionże a Choicejest dołączone przez Choicemodel. Próbowałem użyć belongs_to :question, through: :answerdo wykonania tej czynności.

class User
  has_many :questions
  has_many :choices
end

class Question
  belongs_to :user
  has_many :answers
  has_one :choice, :through => :answer
end

class Answer
  belongs_to :question
end

class Choice
  belongs_to :user
  belongs_to :answer
  belongs_to :question, :through => :answer

  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
end

staje się

NameError uninitialized constant User::Choice

kiedy próbuję to zrobić current_user.choices

Działa dobrze, jeśli nie dołączę rozszerzenia

belongs_to :question, :through => :answer

Ale chcę tego użyć, ponieważ chcę mieć możliwość wykonania validates_uniqueness_of

Prawdopodobnie przeoczę coś prostego. Każda pomoc będzie mile widziana.


1
Może warto zmienić zaakceptowaną odpowiedź na delegata?
domu

Odpowiedzi:


60

belongs_toStowarzyszenie nie może mieć :throughopcję. Lepiej jest buforować question_idwłączone Choicei dodać unikalny indeks do tabeli (zwłaszcza, że validates_uniqueness_ofjest podatny na warunki wyścigu).

Jeśli masz paranoję, dodaj niestandardową weryfikację, Choicektóra potwierdzi, że odpowiedź jest question_idzgodna, ale wygląda na to, że użytkownik nigdy nie powinien mieć możliwości przesłania danych, które spowodowałyby tego rodzaju niezgodność.


Dzięki Stephen, naprawdę nie chciałem kojarzyć się bezpośrednio z question_id, ale myślę, że to najłatwiejszy sposób. Moja pierwotna myśl była taka, że ​​skoro „odpowiedź” należy do „pytania”, zawsze mogę przejść przez „odpowiedź”, aby dostać się do „pytania”. Ale czy myślisz, że nie jest to łatwe do zrobienia, czy myślisz, że to po prostu zły schemat?
vinhboy,

Jeśli chcesz mieć unikalne ograniczenie / walidacje, pola o określonym zakresie muszą istnieć w tej samej tabeli. Pamiętaj, są warunki wyścigu.
stephencelis

1
>> Wygląda na to, że użytkownik nigdy nie powinien mieć możliwości przesłania danych, które spowodowałyby tego rodzaju niezgodności. - Nigdy nie możesz zagwarantować, że użytkownik „nie ma możliwości zrobienia czegoś”, chyba że wykonasz w tym celu jawną kontrolę po stronie serwera.
Konstantin

376

Możesz również delegować:

class Company < ActiveRecord::Base
  has_many :employees
  has_many :dogs, :through => :employees
end

class Employee < ActiveRescord::Base
  belongs_to :company
  has_many :dogs
end

class Dog < ActiveRecord::Base
  belongs_to :employee

  delegate :company, :to => :employee, :allow_nil => true
end

27
+1, to najczystszy sposób na zrobienie tego. (przynajmniej tak mi się wydaje)
Orlando,

9
Czy jest sposób, aby to zrobić za pomocą JOIN, aby nie używać tylu zapytań?
Tallboy

1
Chciałbym poznać siebie. Wszystko, czego próbowałem, odpalił 3 wybory. Możesz określić lambdę "-> {joins: something}" w asocjacji. Łączenie jest uruchamiane, ale później i tak następuje wybór. Nie mogłem tego dostroić.
Renra

2
@Tallboy Kilka doskonale zindeksowanych zapytań wybierających na kluczach podstawowych jest prawie zawsze lepszych niż jakiekolwiek pojedyncze zapytanie JOIN. Połączenia sprawiają, że baza danych działa ciężko.
Ryan McGeary

1
Co robi allow_nil? Czy pracownik nie powinien mieć zawsze firmy?
kodowanie

115

Po prostu użyj has_onezamiast belongs_tow swoim :through, na przykład:

class Choice
  belongs_to :user
  belongs_to :answer
  has_one :question, :through => :answer
end

Bez związku, ale wahałbym się, czy użyć validates_uniqueness_of zamiast używać odpowiedniego unikalnego ograniczenia w Twojej bazie danych. Kiedy robisz to w rubinie, masz warunki wyścigu.


38
Duże ostrzeżenie przy tym rozwiązaniu. Za każdym razem, gdy zapiszesz Wybór, zawsze zapisze Pytanie, chyba że autosave: falsejest ustawione.
Chris Nicola,

@ChrisNicola, czy możesz wyjaśnić, co masz na myśli, nie rozumiem, co masz na myśli.
aks

Co miałem na myśli, gdzie? Jeśli masz na myśli odpowiednie unikalne ograniczenie, mam na myśli dodanie UNIQUE indeksu do kolumny / pola, które musi być unikalne w bazie danych.
Chris Nicola

4

Moje podejście polegało na stworzeniu wirtualnego atrybutu zamiast dodawania kolumn bazy danych.

class Choice
  belongs_to :user
  belongs_to :answer

  # ------- Helpers -------
  def question
    answer.question
  end

  # extra sugar
  def question_id
    answer.question_id
  end
end

To podejście jest dość proste, ale wiąże się z kompromisami. Wymaga załadowania Railsów answerz bazy danych, a następnie question. Można to później zoptymalizować, chętnie ładując potrzebne skojarzenia (tj. c = Choice.first(include: {answer: :question})), Jednak jeśli ta optymalizacja jest konieczna, wówczas odpowiedź Stephencelisa jest prawdopodobnie lepszą decyzją dotyczącą wydajności.

Jest czas i miejsce na pewne wybory i myślę, że ten wybór jest lepszy przy prototypowaniu. Nie użyłbym go do kodu produkcyjnego, chyba że wiedziałbym, że jest to rzadki przypadek użycia.


1

Wygląda na to, że chcesz, aby użytkownik miał wiele pytań.
Pytanie ma wiele odpowiedzi, z których jedną jest wybór użytkownika.

Czy tego szukasz?

Wymodelowałbym coś takiego w następujący sposób:

class User
  has_many :questions
end

class Question
  belongs_to :user
  has_many   :answers
  has_one    :choice, :class_name => "Answer"

  validates_inclusion_of :choice, :in => lambda { answers }
end

class Answer
  belongs_to :question
end

1

Więc nie możesz zachowywać się tak, jak chcesz, ale możesz zrobić coś, na co masz ochotę. Chcesz móc to zrobićChoice.first.question

to, co zrobiłem w przeszłości, przypomina coś takiego

class Choice
  belongs_to :user
  belongs_to :answer
  validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
  ...
  def question
    answer.question
  end
end

w ten sposób możesz teraz zadać pytanie o wybór


-1

has_many :choicesTworzy stowarzyszenie nazwane choices, nie choice. Spróbuj użyć current_user.choiceszamiast tego.

Zobacz dokumentację ActiveRecord :: Associations, aby uzyskać informacje o has_manymagii.


1
Dziękuję za pomoc Michael, jednak to z mojej strony literówka. Już robię current_user.choices. Ten błąd ma coś wspólnego z tym, że chcę przypisać przynależność do użytkownika i pytania.
vinhboy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.