【问题标题】:How to add a method to an activerecord collection?如何将方法添加到 activerecord 集合?
【发布时间】:2013-10-20 14:12:45
【问题描述】:

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

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

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

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

    您可能想查看“Observer Classes


    我写了一篇关于他们的帖子here

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

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

    【讨论】:

      【解决方案2】:

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

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

      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。基本上用scoped替换@relation

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

      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)
      

      【讨论】:

      • 我的问题很糟糕 :( 我真的在研究“@relation”是否好用,或者是否有更好的方法向集合添加方法。不过我真的很喜欢你的帖子。
      • 它给了我一个 Rails 4 的错误,但我发现了问题:github.com/voxdolo/decent_exposure/pull/73 感谢您的帮助以及来自 codeclimate 的精彩博客文章的链接!
      • 我的错。在 Rails 4 中不推荐使用 scoped,现在您需要使用 all。它看起来有点奇怪,但我刚刚检查过它并且它有效。
      • UPD2 不要在集合上使用.map(&amp;:id),而是使用.pluck(:id)
      【解决方案3】:

      在 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这样的代码

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-29
        • 1970-01-01
        • 2022-11-16
        相关资源
        最近更新 更多