How to add a method to an activerecord collection?
我想为特定模型的所有集合添加一个方法。 假设我想将方法
1 2 | WeatherData.all.limit(3).my_complicated_averaging_method() Station.first.weatherdata.my_complicated_averaging_method() |
做这个的最好方式是什么? 目前,我找到的唯一方法是这样的:
1 2 3 4 5 6 7 8 9 10 | 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 |
这是将方法添加到集合的好方法吗? 有没有更好/支持的方式来做到这一点?
有很多方法可以做到这一点,你的完全有效(尽管我个人更喜欢将类方法包装到单独的块中检查一下),但是随着人们为他们的模型添加更多的业务逻辑并盲目地遵循"瘦控制器,胖模型"概念,模型变成完全混乱。
为了避免这种混乱,引入服务对象是一个好主意,在你的情况下,它将是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
现在,您可以通过将您的集合传递给它来直接调用此类。但您也可以像这样代理呼叫:
1 2 3 | 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
你是正确的使用实例变量是一种可能的方式搞乱对象内部(加上它不是一个公共接口,它可能会在未来发生变化)。我的建议是使用方法
检查此示例。我使用了自己项目中的模型来证明它确实有效
1 2 3 4 5 6 7 8 9 10 11 12 | 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中,不推荐使用
1 | all.map(&:id) |
在Rails> = 4上,您可以使用
1 2 3 4 5 6 7 | class Foo < ActiveRecord::Base def self.bar where(nil).pluck(:id) end end Foo.where(id: [1, 2, 3]).order(:id).bar |
此外,您可以使用
1 2 3 | class Foo < ActiveRecord::Base scope :bar, -> {where(nil).pluck(:id)} end |
最后,您可以编写类似
让事情发挥作用似乎很好,但为了更加精细,我相信有更好的东西。不可否认,你并没有过于具体地描述你想要实现的目标,所以我只能给你这个广泛的建议
您可能想要查看"观察者类"
我在这里写了一篇关于他们的帖子
Observer Classes basically monitor a particular model function & extend it. I think they're only for the before_filter, etc functions, but I don't see why you can't extend indvidual functions that you create
您必须使用rails 4.0+中的