Recentemente, eu li o artigo muito bom de Justin Weiss sobre Como pré-carregar escopos Rails . A essência é esta – se você tiver:
class Product < ActiveRecord::Base
has_many :ratings
end
e
class Rating < ActiveRecord::Base
belongs_to :product
end
em vez de ter um scope
interior Rating
como este:
scope :positive, -> { where 'score > 3.0'}
você pode ter uma associação com escopo dentro Product
has_many :positive_ratings, -> { where 'score > 3.0'}, class_name: 'Rating'
A razão para isso é que, no primeiro caso, você chamaria as avaliações positivas para um produto como este:
product.ratings.positive
e no segundo caso assim:
product.positive_ratings
Isso também permite que você os carregue rapidamente assim:
Product.includes(:positive_ratings).
o que não é possível no primeiro caso.
Uma reviravolta ainda mais interessante: e se você tiver uma associação entre dois modelos e o modelo também usar o padrão de Herança de tabela única ?has_many :through
through
Vamos ver na prática:
class User < ActiveRecord::Base
has_many :activities, inverse_of: :user
has_many :products, through: :activities
end
class Activity < ActiveRecord::Base
belongs_to :user, inverse_of: :activities
belongs_to :product, inverse_of: :activities
end
class View < Activity; end
class Save < Activity; end
class Product < ActiveRecord::Base
has_many :activities, inverse_of: :product
has_many :users, through: :activities
end
E se você quiser obter apenas os produtos salvos para um usuário? Você pode adicionar o seguinte método a User
:
def saved_products
products.includes(:activities).where(activities: {type: 'Save'})
end
No entanto, você não pode pré-carregar os produtos salvos e você teria o problema, ao carregar alguns usuários e exibir seus produtos salvos. Vamos consertar isso.N+1
Primeiro, vamos adicionar a associação com escopo entre User
e Activity
para User
:
has_many :saves, -> { where type: 'Save' }, class_name: 'Activity'
Isso nos permitirá carregar antecipadamente nossos produtos salvos como este:
User.includes(saves: :product).first
Para que seja ainda mais conveniente, você precisa dar o próximo passo e adicionar a associação com escopo entre User
e Product
:
has_many :saved_products, through: :saves, source: :product
A etapa principal aqui é adicionar a source
opção. Isto:
Especifica o nome da associação de origem usado por várias: através de consultas. Use-o apenas se o nome não puder ser inferido da associação. tem muitos: assinantes, por meio de:: assinaturas procurarão: assinantes ou: assinante em Assinatura, a menos que um: source seja fornecido.
Uma vez Activity
que não tem uma associação, chamada ‘VistoProduto’, você precisa especificar o nome correto usando source
.
Agora você pode apenas ligar para:
User.includes(:saved_products).first
Feliz pré-carregamento!