Chętny ładunek polimorficzny

104

Używając Railsów 3.2, co jest nie tak z tym kodem?

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')

Rodzi ten błąd:

Nie mogę chętnie załadować skojarzenia polimorficznego: przeglądalny

Jeśli usunę reviewable.shop_type = ?warunek, działa.

Jak mogę filtrować na podstawie reviewable_typei reviewable.shop_type(czyli w rzeczywistości shop.shop_type)?

Zwycięzca
źródło

Odpowiedzi:

207

Domyślam się, że Twoje modele wyglądają tak:

class User < ActiveRecord::Base
  has_many :reviews
end

class Review < ActiveRecord::Base
  belongs_to :user
  belongs_to :reviewable, polymorphic: true
end

class Shop < ActiveRecord::Base
  has_many :reviews, as: :reviewable
end

Nie możesz wykonać tego zapytania z kilku powodów.

  1. ActiveRecord nie może zbudować sprzężenia bez dodatkowych informacji.
  2. Nie ma tabeli o nazwie do przeglądania

Aby rozwiązać ten problem, musisz wyraźnie zdefiniować relację między Reviewi Shop.

class Review < ActiveRecord::Base
   belongs_to :user
   belongs_to :reviewable, polymorphic: true
   # For Rails < 4
   belongs_to :shop, foreign_key: 'reviewable_id', conditions: "reviews.reviewable_type = 'Shop'"
   # For Rails >= 4
   belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
   # Ensure review.shop returns nil unless review.reviewable_type == "Shop"
   def shop
     return unless reviewable_type == "Shop"
     super
   end
end

Następnie możesz zapytać w ten sposób:

Review.includes(:shop).where(shops: {shop_type: 'cafe'})

Zwróć uwagę, że nazwa tabeli to shopsi nie reviewable. W bazie danych nie powinno być tabeli o nazwie przeglądalność.

Uważam, że jest to łatwiejsze i bardziej elastyczne niż jawne definiowanie joinmiędzy, Reviewa Shopponieważ pozwala na niecierpliwe ładowanie oprócz zapytań z powiązanych pól.

Powodem, dla którego jest to konieczne, jest to, że ActiveRecord nie może zbudować sprzężenia na podstawie samego przeglądu, ponieważ wiele tabel reprezentuje drugi koniec sprzężenia, a SQL, o ile wiem, nie pozwala na dołączenie do tabeli nazwanej przez przechowywaną wartość w kolumnie. Definiując dodatkową relację belongs_to :shop, podajesz ActiveRecord informacje potrzebne do zakończenia łączenia.

Sean Hill
źródło
6
Właściwie skończyło się na tym, że użyłem tego bez deklarowania niczego więcej:@reviews = @user.reviews.joins("INNER JOIN shops ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '" + type + "')").includes(:user, :reviewable => :photos)
Victor
1
To dlatego, że :reviewablejest Shop. Zdjęcia należą do sklepu.
Victor
6
działał w rails4, ale wyświetli ostrzeżenie o wycofaniu, powinien używać stylu has_many: spam_comments, -> {gdzie spam: true}, class_name: 'Comment'. Więc w rails4, będzie należeć do: sklep, -> {gdzie ("reviews.reviewable_type = 'Shop'")}, Foreign_key: 'reviewable_id'. Ale uważaj, Review.includes (: shop) spowoduje błąd, musi dołączyć co najmniej jedną klauzulę gdzie.
raykin
49
Jest też typ obcy, który zadziałał u mnie przy podobnym problemie:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y
14
Podczas ładowania, w reviewstym zachłannego ładowania skojarzonego shopza pomocą kodu, Review.includes(:shop) definicja allowed_to belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' zgłasza błąd mówiąc missing FROM-clause entry for table "reviews". Naprawiłem to, aktualizując definicję belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
should_to
12

Jeśli otrzymasz ActiveRecord :: EagerLoadPolymorphicError, dzieje się tak, ponieważ includeszdecydowano się wywołać, eager_loadgdy skojarzenia polimorficzne są obsługiwane tylko przez preload. Jest w dokumentacji tutaj: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Dlatego zawsze używaj preloaddo skojarzeń polimorficznych. Jest jedno zastrzeżenie: nie można zapytać o skojarzenie polimorficzne w klauzulach gdzie (co ma sens, ponieważ asocjacja polimorficzna reprezentuje wiele tabel).

seanmorton
źródło
Widzę, że to jedyna metoda, która nie została udokumentowana w przewodnikach: guide.rubyonrails.org/ ...
MSC
0
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Kiedy używasz fragmentów SQL z WHERE, do przyłączenia się do Twojego stowarzyszenia niezbędne są odwołania.

un_gars_la_cour
źródło
0

Jako uzupełnienie odpowiedzi u góry, która jest doskonała, możesz również określić :include w skojarzeniu, jeśli z jakiegoś powodu zapytanie, którego używasz, nie obejmuje tabeli modelu i otrzymujesz niezdefiniowane błędy tabeli.

Tak jak to:

belongs_to :shop, 
           foreign_key: 'reviewable_id', 
           conditions: "reviews.reviewable_type = 'Shop'",
           include: :reviews

Bez tej :includeopcji, jeśli po prostu uzyskasz dostęp do asocjacji review.shopz powyższego przykładu, otrzymasz błąd UndefinedTable (testowany w Railsach 3, a nie 4), ponieważ skojarzenie to zrobi SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

:includeOpcja zmusi JOIN zamiast. :)

Stewart Mckinney
źródło
5
Nieznany klucz:: warunki. Prawidłowe klucze to:: class_name,: class,: Foreign_key,: validate,: autosave,: dependent,: primary_key,: inverse_of,: required,: Foreign_type,: polymorphic,: touch
,: counter_cache