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.
- ActiveRecord nie może zbudować sprzężenia bez dodatkowych informacji.
- Nie ma tabeli o nazwie do przeglądania
Aby rozwiązać ten problem, musisz wyraźnie zdefiniować relację między Review
i 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 shops
i 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 join
między, Review
a Shop
ponieważ 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.
@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)
:reviewable
jestShop
. Zdjęcia należą do sklepu.belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
reviews
tym zachłannego ładowania skojarzonegoshop
za pomocą kodu,Review.includes(:shop)
definicja allowed_tobelongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
zgłasza błąd mówiącmissing 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'
Jeśli otrzymasz ActiveRecord :: EagerLoadPolymorphicError, dzieje się tak, ponieważ
includes
zdecydowano się wywołać,eager_load
gdy skojarzenia polimorficzne są obsługiwane tylko przezpreload
. Jest w dokumentacji tutaj: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.htmlDlatego zawsze używaj
preload
do 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).źródło
Kiedy używasz fragmentów SQL z WHERE, do przyłączenia się do Twojego stowarzyszenia niezbędne są odwołania.
źródło
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:
Bez tej
:include
opcji, jeśli po prostu uzyskasz dostęp do asocjacjireview.shop
z powyższego przykładu, otrzymasz błąd UndefinedTable (testowany w Railsach 3, a nie 4), ponieważ skojarzenie to zrobiSELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )
.:include
Opcja zmusi JOIN zamiast. :)źródło