2013-04-20 5 views
78

Использование Rails 3.2, что не так с этим кодом?Eager load polymorphic

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

Она поднимает эту ошибку:

Can not eagerly load the polymorphic association :reviewable

Если удалить условие reviewable.shop_type = ?, он работает.

Как я могу фильтровать на основе reviewable_type и reviewable.shop_type (это на самом деле shop.shop_type)?

ответ

161

Я думаю, что ваши модели выглядеть следующим образом:

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 

Вы не в состоянии сделать этот запрос по нескольким причинам.

  1. ActiveRecord не может создавать соединение без дополнительной информации.
  2. Там нет таблицы называется пересмотренным

Чтобы решить эту проблему, необходимо явно определить отношения между Review и 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 

Тогда вы можете запросить так:

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

Обратите внимание, что имя таблицы shops и не reviewable. Не должно быть таблицы, которую можно просмотреть в базе данных.

Я считаю, что это было проще и гибче, чем прямое определение join между Review и Shop, поскольку оно позволяет загружать запросы в дополнение к запросам по связанным полям.

Причина, по которой это необходимо, заключается в том, что ActiveRecord не может создать соединение на основе только рассмотренных, поскольку несколько таблиц представляют другой конец соединения, а SQL, насколько мне известно, не позволяет вам присоединиться к таблице с именем по значению, хранящемуся в столбце. Определив дополнительную связь belongs_to :shop, вы предоставляете ActiveRecord информацию, необходимую для завершения соединения.

+3

Безупречный ответ и подробное объяснение. Благодарю. – Victor

+4

На самом деле я закончил использование этого, не объявив ничего больше: '@reviews = @ user.reviews.joins (" INNER JOIN магазины ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '"+ type + "')") включает (: user,: reviewable =>: photos) ' – Victor

+0

Это работает? –

1

В дополнении к ответу наверху, который является превосходным, вы также можете указать :include об ассоциации, если по какой-либо причине используемый вами запрос не включает таблицу модели, и вы получаете неопределенные ошибки таблицы.

Как так:

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

Без опции :include, если вы просто получить доступ к ассоциации review.shop в приведенном выше примере, вы получите UndefinedTable ошибку (протестировано в Rails 3, не 4), потому что ассоциация будет do SELECT FROM shops WHERE shop.id = 1 AND (reviews.review_type = 'Shop').

Опция :include будет заставлять использовать JOIN.:)

+2

Неизвестный ключ:: условия. Допустимыми ключами являются:: class_name,: class,: foreign_key,: validate,: autosave,: dependent,: primary_key,: inverse_of,: required,: foreign_type,: polymorphic,: touch,: counter_cache – Bengala

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

Когда вы используете SQL-фрагменты с WHERE, ссылки необходимы для присоединения к вашей ассоциации.

0

Если вы получите ActiveRecord :: EagerLoadPolymorphicError, это потому, что includes решил назвать eager_load когда полиморфные ассоциации поддерживаются только preload. Он находится в документации: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Так что всегда используйте preload для полиморфных ассоциаций. Для этого существует одно предостережение: вы не можете запрашивать полиморфное объединение в тех случаях, когда клаузулы (что имеет смысл, поскольку полиморфная ассоциация представляет собой несколько таблиц.)