18

我想为特定模型的所有集合添加一个方法。假设我想将该方法添加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

这是向集合添加方法的好方法吗?有没有更好/受支持的方法来做到这一点?

4

3 回答 3

15

有很多方法可以做到这一点,你的方法是完全有效的(虽然我个人更喜欢将类方法包装到单独的块中,检查一下),但是随着人们在他们的模型中添加更多的业务逻辑并盲目地遵循“瘦控制器,胖模特”的概念,模特变得一团糟。

为了避免这种混乱,引入服务对象是一个好主意,在你的情况下,它会是这样的:

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

你是对的,使用实例变量是搞乱对象内部结构的一种可能方式(而且它不是公共接口,将来可能会改变)。我在这里的建议是使用 method scoped。基本上替换@relationscoped.

检查这个例子。我使用了我自己项目中的模型来证明它确实有效

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 4scoped中已弃用,因此使用all.

all.map(&:id)
于 2013-10-12T17:07:28.503 回答
5

在 Rails >= 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

于 2016-11-18T16:38:19.603 回答
0

让事情运转起来看起来不错,但为了更巧妙,我相信会有更好的东西。诚然,你并没有太具体地描述你想用这个实现什么,所以我只能给你这个广泛的建议

您可能想查看“观察者类


我在这里写了一篇关于他们的文章

观察者类基本上监视特定的模型功能并对其进行扩展。我认为它们仅适用于 before_filter 等函数,但我不明白为什么你不能扩展你创建的单个函数

您必须使用rails 4.0+ 中rails-observersgem才能使它们正常工作,因为它们已从 rails 核心中贬值

于 2013-10-12T15:59:57.703 回答