Meu palpite é que seus modelos são assim:
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
Você não pode fazer essa consulta por vários motivos.
- ActiveRecord não é capaz de construir a junção sem informações adicionais.
- Não há nenhuma tabela chamada revisável
Para resolver esse problema, você precisa definir explicitamente a relação entre Review
e 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
Então você pode consultar assim:
Review.includes(:shop).where(shops: {shop_type: 'cafe'})
Observe que o nome da tabela é shops
e não reviewable
. Não deve haver uma tabela chamada revisável no banco de dados.
Acredito que isso seja mais fácil e flexível do que definir explicitamente o join
entre Review
e, Shop
pois permite que você carregue com antecedência, além de consultar por campos relacionados.
A razão pela qual isso é necessário é que ActiveRecord não pode construir uma junção baseada apenas em revisável, uma vez que várias tabelas representam a outra extremidade da junção, e o SQL, até onde eu sei, não permite que você junte uma tabela nomeada pelo valor armazenado em uma coluna. Ao definir o relacionamento extra belongs_to :shop
, você está fornecendo ao ActiveRecord as informações de que precisa para concluir a associação.
@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
éShop
. As fotos pertencem à loja.belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
reviews
incluindo o carregamento antecipado doshop
código associado ,Review.includes(:shop)
a definição belongs_tobelongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
gera um erro dizendomissing FROM-clause entry for table "reviews"
. Eubelongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Se você obtiver um ActiveRecord :: EagerLoadPolymorphicError, é porque
includes
decidiu chamareager_load
quando as associações polimórficas são suportadas apenas porpreload
. Está na documentação aqui: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.htmlPortanto, sempre use
preload
para associações polimórficas. Há uma advertência para isso: você não pode consultar a associação polimórfica nas cláusulas where (o que faz sentido, já que a associação polimórfica representa várias tabelas).fonte
Quando você está usando fragmentos SQL com WHERE, referências são necessárias para ingressar em sua associação.
fonte
Como adendo a resposta no topo, que é excelente, você também pode especificar
:include
na associação se por algum motivo a consulta que você está usando não inclui a tabela do modelo e você está obtendo erros de tabela indefinida.Igual a:
Sem a
:include
opção, se você simplesmente acessar a associaçãoreview.shop
no exemplo acima, obterá um erro UndefinedTable (testado no Rails 3, não 4) porque a associação serviráSELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )
.A
:include
opção irá forçar um JOIN. :)fonte