我想为特定模型的所有集合添加一个方法。假设我想将方法my_complicated_averaging_method
添加到WeatherData集合中:
WeatherData.all.limit(3).my_complicated_averaging_method()
Station.first.weatherdata.my_complicated_averaging_method()
最好的方法是什么?目前,我找到的唯一方法就是这样:
class WeatherData < ActiveRecord::Base
def self.my_complicated_averaging_method
weighted_average = 0
@relation.each do |post|
# do something complicated
# weighted_average =
end
return weighted_average
end
end
这是将方法添加到集合的好方法吗?有没有更好/支持的方式来做到这一点?
答案 0 :(得分:12)
有很多方法可以做到这一点,你的完全有效(虽然我个人更喜欢将类方法包装到单独的块检查this中),但是随着人们为他们的模型添加更多的业务逻辑并盲目地遵循“瘦瘦的控制器,胖模型”的概念,模型变成完全一团糟。
为了避免这种混乱,引入服务对象是个好主意,在你的情况下它会是这样的:
class AverageWeatherData
class << self
def data(collection)
new(collection).data
end
end
def initialize(collection)
@collection = collection
end
def data
@collection.reduce do |avg, post|
# reduce goes through every post, each next iteration receives in avg a value of the last line of iteration
# do something with avg and post
end
# no need for explicit return, every line of Ruby code returns it's value
# so this method would return result of the reduce
# more on reduce: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-reduce
end
end
现在您可以通过将您的集合传递给它来直接调用此类。但你也可以像这样代理电话:
def self.my_complicated_averaging_method
AverageWeatherData.data(@relation)
end
我鼓励您通过阅读此博客了解更多此方法: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
<强> UPD 强>
你是正确的使用实例变量是一种可能的方式来搞乱对象内部(加上它不是一个公共接口,它可能在将来改变)。我的建议是使用方法scoped
。基本上将@relation
替换为scoped
。
检查此示例。我使用了我自己项目中的模型来证明它确实有效
2.0.0p247 :001 > Tracking # just asking console to load this class before modifying it
# => Tracking(id: integer, action: string, cookie_id: string, ext_object_id: integer, created_at: datetime, updated_at: datetime)
2.0.0p247 :002 > class Tracking
2.0.0p247 :003?> def self.fetch_ids
2.0.0p247 :004?> scoped.map(&:id)
2.0.0p247 :005?> end
2.0.0p247 :006?> end
# => nil
2.0.0p247 :007 >
2.0.0p247 :008 > Tracking.where(id: (1..100)).fetch_ids
# Tracking Load (2.0ms) SELECT "trackings".* FROM "trackings" WHERE ("trackings"."id" BETWEEN 1 AND 100)
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
<强> UPD 强>
在Rails 4中scoped
已被弃用,因此使用all
是正确的。
all.map(&:id)
答案 1 :(得分:2)
在Rails&gt; = 4上,您可以使用where(nil)
代替scoped
class Foo < ActiveRecord::Base
def self.bar
where(nil).pluck(:id)
end
end
Foo.where(id: [1, 2, 3]).order(:id).bar
此外,您可以使用#scope
,例如:
class Foo < ActiveRecord::Base
scope :bar, -> {where(nil).pluck(:id)}
end
最后,您可以编写类似Foo.all.bar
答案 2 :(得分:0)
让事情发挥作用看起来不错,但为了更加精细,我确信有更好的东西。不可否认,你并没有太具体地描述你想要实现的目标,所以我只能给你这个广泛的建议
您可能需要查看“ Observer Classes ”
我写了一篇关于他们的帖子here
观察者类基本上监视特定的模型函数&amp;延伸它。我认为它们只适用于before_filter等功能,但我不明白为什么你不能扩展你创建的单个函数
你必须在rails 4.0+中使用the rails-observers
gem才能让它们正常工作,因为它们已经从rails core中折旧了