如何将方法添加到activerecord集合? [英] How to add a method to an activerecord collection?

查看:91
本文介绍了如何将方法添加到activerecord集合?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想为特定模型的所有集合添加一个方法。假设我想将方法​​ my_complicated_averaging_method 添加到WeatherData集合中:

I would like to add a method to all collections for a specific model. Let's say I want to add the method my_complicated_averaging_method to the WeatherData collections:

WeatherData.all.limit(3).my_complicated_averaging_method()
Station.first.weatherdata.my_complicated_averaging_method()

执行此操作的最佳方法是什么?目前,我找到的唯一方法是这样的:

What is the best way to do this? At the moment the only way I've found is like this:

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

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

Is this a good way for adding a method to a collection? Is there a better / supported way to do this?

推荐答案

有很多方法可以做到,而您的完全有效(尽管我个人更喜欢将类方法包装到单独的块检查中 this out),但是随着人们在其模型中添加更多的业务逻辑并盲目遵循瘦控制器,胖模型的概念,模型变成了完全混乱的局面。

There a lot of ways how to do it, and Yours is completely valid (though I personally prefer to wrap class methods into separate block check this out ), but as people are adding more business logic to their models and blindly follow "skinny controllers, fat models" concept, models turn into complete mess.

为避免这种混乱局面,引入服务对象的好主意,在您的情况下将是这样的:

To avoid this mess it's a good idea to introduce service objects, in Your case it would be like 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

现在您可以通过将您的集合传递给该类,直接调用该类。但是您也可以像这样代理呼叫:

Now You can call this class directly by passing Your collection to it. But You can also proxy the call like this:

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/

I encourage You to lear more of this approach by reading this blog: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

UPD

使用实例变量是正确的,可以使对象内部混乱(另外,它不是公共接口,将来可能会更改)。我的建议是使用 scoped 方法。基本上将 @relation 替换为范围内的

You are right using instance variable is a possible way to mess up object internals(plus it's not a public interface and it might change in future). My proposal here is to use method scoped. Basically replace @relation with scoped.

检查此例。我已经使用自己项目中的模型来证明它确实有效

Check this example. I have used a model from my own project to show that it actually works

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中作用域已弃用,因此使用 all 是正确的。

In Rails 4 scoped is deprecated, so it's correct to use all.

all.map(&:id)

这篇关于如何将方法添加到activerecord集合?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆