使用范围重构 has_many [英] Refactoring has_many with scopes
问题描述
我是新手,我刚刚向专家展示了我的代码,专家告诉我我不应该使用 has_many
来过滤我的变量,而是使用 scopes
.
I'm a newbie and I just showed my code to an expert, that told me I shouldn't use has_many
to filter my variables, but scopes
.
我有三个模型:用户、产品和所有权.
I have three models : User, Product and Ownership.
这是我在 app/models/user.rb 中的代码:
So here is my code in app/models/user.rb :
class User
has_many :ownerships, foreign_key: "offerer_id",
dependent: :destroy
has_many :owned_products, through: :ownerships,
source: :product
has_many :future_ownerships, -> { where owning_date: nil, giving_date: nil },
class_name: "Ownership",
foreign_key: "offerer_id"
has_many :wanted_products, through: :future_ownerships,
source: :product
end
所以我删除了 has_many :future_ownerships
和 has_many :wanted_products
,并在 app/models/ownership.rb 中创建了一个范围:
So I deleted the has_many :future_ownerships
and has_many :wanted_products
, and created a scope in app/models/ownership.rb :
class Ownership
scope :future, -> { where owning_date: nil, giving_date: nil }
end
现在我可以找到未来的所有权:user.ownerships.future
.但我不知道的是,如何检索想要的产品?如何在我的 app/models/product.rb 中创建一个范围以便能够输入类似的内容:
Now I can find the future ownerships doing this : user.ownerships.future
. But what I don't know, is how to retrieve the wanted products ? How can I make a scope in my app/models/product.rb to be able to type something like that :
user.owned_products.wanted
推荐答案
关联中的条件本身并没有什么不好的,特别是如果您需要预先加载产品的子集.
There's nothing inherently bad with conditions in your associations, specially if you need to eager load a subset of products.
然而,要实现您需要的范围,您必须将其添加到 Product
模型中,并使用普通 sql,因为过滤器应用于与它定义的模型不同的模型.
However to achieve the scope you need, you must add it on the Product
model and resort to plain sql since the filter is applied on a different model than the one it's defined on.
class Product
# not tested
scope :wanted, ->{ where("ownerships.owning_dates IS NULL AND ...") }
end
恕我直言,您最好使用第一个解决方案.原因是,如果出于某种原因,您将该范围应用到许多用户的块中,尽管急于加载产品,您仍会遇到 O(n) 墙.
IMHO you're better off with the first solution. The reason is, if for some reason you apply that scope inside a block of many users, you'll hit the O(n) wall despite eager loading the products.
User.includes(:owned_products).each do |user|
user.onwned_products.wanted # => SQL connection
end
更新:刚刚发现merge
一个惊人的未记录的 ActiveRecord 功能.
Update : just found out about merge
an amazingly undocumented feature of ActiveRecord.
在其他用途中,它允许您进行连接,并通过连接模型上的命名范围进行过滤
Among other uses, it allows you to do a join, and filter by a named scope on the joined model
换句话说,你可以这样做:
In other words you can do :
user.owned_products.merge(Ownership.future)
放弃强大!
这篇关于使用范围重构 has_many的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!