2013-10-12 9 views
15

特定のモデルのすべてのコレクションにメソッドを追加したいと思います。のは、私がWeatherDataコレクションに方法my_complicated_averaging_methodを追加したいとしましょう:activerecordコレクションにメソッドを追加する方法は?

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 

は、このコレクションにメソッドを追加するための良い方法ですか?これを行うためのより良い/サポートされた方法がありますか?

答えて

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を使用することです。基本的に@relationscopedに置き換えてください。

この例を確認してください。それはallを使用することが正しいですので、私は、それが実際にRailsでは

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

は4 scoped廃止されて動作することを示すために、私自身のプロジェクトからモデルを使用しています。レール上

all.map(&:id) 
+0

私の質問は悪かった:(私はreaだった"@relation"が使い易かったかどうか、あるいはコレクションにメソッドを追加するためのより良い方法があるかどうかを調べています。私は本当にあなたの投稿が好きです。 – Leon

+0

私は今、理解したことを理解しました。 –

+0

それは私にレール4のエラーを与えますが、私は問題を発見しました:https://github.com/voxdolo/decent_exposure/pull/73 助けとコーディネイトからの素晴らしいブログ記事へのリンクありがとう! – Leon

0

これは機能しているように見えますが、もっと精巧になるためには、はるかに優れたものがあると確信しています。確かに、あなたはこれを達成するために何を望むか説明するのにあまりにも特異的ではなかったので、私は唯一のあなたにこの広範な提案

あなたは「Observer Classes


Iに見てみたいことがありますを与えることができます

hereそれらについてのオブザーバークラスは基本的にそれを拡張し、特定のモデル関数&を監視する記事を書きました。私は、彼らがbefore_filterなどの機能のためだけだと思う​​が、あなたは

を作成indvidualの機能を拡張することはできませんなぜ私はあなたがそれらを得るために4.0以降レールにthe rails-observers gemを使用する必要があります表示されません彼らはレールのコアから償却されているので、作業しています

2

> = 4あなたはscoped

class Foo < ActiveRecord::Base 
    def self.bar 
    where(nil).pluck(:id) 
    end 
end 

Foo.where(id: [1, 2, 3]).order(:id).bar 

where(nil)インプレースを使用することができ、さらに、あなたは例えば、#scopeを使用することができます。最後に

class Foo < ActiveRecord::Base 
    scope :bar, -> {where(nil).pluck(:id)} 
end 

を、あなたは書くことができますコードのようなFoo.all.bar

+1

5.1で動作します –

関連する問題