2012-08-23 18 views
9

私は親モデルの集約フィールドを簡単にキャッシュできる単純なCacheableモジュールを作成しました。このモジュールでは、親オブジェクトが、親レベルでのキャッシュを必要とするフィールドごとにcacheableメソッドとメソッドを実装する必要があります。Rubyメタプログラミングを使用してコールバックをRailsモデルに追加するにはどうすればよいですか?

module Cacheable 
    def cache!(fields, *objects) 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each { |field| objects.each(&:"calc_#{field}") } 
    end 

    def save!(objects) 
    objects.each(&:save!) 
    end 
end 

このモジュールが含まれるActiveRecordモデルにコールバックを追加したいと思います。この方法では、モデルが親モデルのハッシュとキャッシングが必要なフィールド名を実装する必要があります。

def cachebacks(klass, parents) 
    [:after_save, :after_destroy].each do |callback| 
    self.send(callback, proc { cache!(CACHEABLE[klass], self.send(parents)) }) 
    end 
end 

私は手動でのような使用して、両方のコールバックを追加する場合、このアプローチは素晴らしい作品:

after_save proc { cache!(CACHEABLE[Quote], *quotes.all) } 
after_destroy proc { cache!(CACHEABLE[Quote], *quotes.all) } 

しかし、私は、コールバックにこれらを追加するcachebacksメソッドを使用しようとすると、私は次のエラーを受信して​​います。

cachebacks(Quote, "*quotes.all") 

NoMethodError: undefined method `cachebacks' for #<Class:0x007fe7be3f2ae8> 

これらのコールバックをクラスに動的に追加するにはどうすればよいですか?

+0

申し訳ありませんが、最後の部分の意味を理解できませんでした。 'Quote'は関連モデルですか?あなたのクラスが今のように見えるように投稿してください。 –

+0

あなたのヒントに基づいて私が試した答えは物事を説明する必要があります。私はActiveSupport :: Concernのアプローチにも興味があります。 – barelyknown

答えて

6

これはActiveSupport::Concernの場合によく似ています。あなたはを含むクラスのクラスメソッドとして追加するために、わずかにあなたのcachebacks方法を微調整することができます

module Cacheable 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def cachebacks(&block) 
     klass = self 
     [:after_save, :after_destroy].each do |callback| 
     self.send(callback, proc { cache!(CACHEABLE[klass], *klass.instance_eval(&block)) }) 
     end 
    end 
    end 

    def cache!(fields, *objects) 
    # ... 
    end 

    # ... 
end 

それを使用するには:

class Example < ActiveRecord::Base 
    include Cacheable 
    cachebacks { all } 
end 

あなたはcachebacksに渡すブロックは、のコンテキストで実行されますそれを呼び出すクラスです。この例では{ all }Example.allを呼び出し、結果をcache!メソッドに渡すのと同じです。


コメントであなたの質問に答えるために、Concernは、共通のパターンをカプセル化とRailsでの大会を確立します。構文はややエレガントです:

また、クラスとインスタンスのメソッドを自動的に正しく含むために、別の規則を利用しています。 ClassMethodsInstanceMethodsという名前のモジュールでこれらのメソッドを名前空間に指定した場合(すでに見たように、InstanceMethodsはオプションです)、完了です。

最後に、モジュールの依存関係を処理します。このドキュメントは良い例を示していますが、本質的に、含まれているクラスが実際に関心のあるモジュールに加えて、依存するモジュールを明示的に含める必要がなくなります。

+1

これは良い解決策です。以前はActiveSupport :: Concernsを使用していませんでした。私が下記に投稿したようなアプローチ(Cacheableモジュール)の主な利点は何ですか? – barelyknown

+0

私は、 'Concern'についてもう少し詳細に答えを更新しました。 – Brandan

0

私はあなたの質問を理解していない場合、私はコメントで言ったように、私は正しくないかもしれません。これはあなたのために働くでしょうか?

module Cacheable 
    def self.included(base) 
    base.class_eval do 
     def self.cachebacks(klass, parents) 
     [:after_save, :after_destroy].each do |callback| 
      self.send(callback, proc { cache!(CACHEABLE[klass], self.send(parents)) }) 
     end 
     end 
    end 
    end 

    def cache!(fields, *objects) 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each { |field| objects.each(&:"calc_#{field}") } 
    end 

    def save!(objects) 
    objects.each(&:save!) 
    end 
end 
+0

それは正確に解決策ではありませんでしたが、あなたの答えは私がそれを理解するのを助けました。下の完全なソリューションを別の回答として追加します。あなたがあなたのことを調整したら、私はそれを受け入れたものとしてマークします。本当にありがとう。 – barelyknown

+0

「自己」を忘れました。それは動作するはずです。 :P –

1

私は、ソリューション。

モデルを次のモデルに追加します。モデルごとに複数の親関係をcachebackすることができます。また、特定のフィールドの文字列の代わりにハッシュを渡すことで、親テーブルと子テーブルに異なる属性名を指定することもできます。

include Cacheable 
cacheback(parent: :quotes, fields: %w(weight pallet_spots value equipment_type_id)) 

このモジュールはactivesupportの::懸念を拡張し、コールバックを追加し、cacheingを行います。あなたの親クラスは、キャッシング作業を行うためにcalc_fieldメソッドを実装する必要があります。

module Cacheable 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def cacheback(options) 
     fields = Cacheable.normalize_fields(options[:fields]) 
     [:after_save, :after_destroy].each do |callback| 
     self.send(callback, proc { cache!(fields, self.send(options[:parent])) }) 
     end 
    end 
    end 

    def cache!(fields, objects) 
    objects = objects.respond_to?(:to_a) ? objects.to_a : [objects] 
    objects.each do |object| 
     if object.cacheable? 
     calc(fields, objects) 
     save!(objects) 
     end 
    end 
    end 

    def calc(fields, objects) 
    fields.each do |parent_field, child_field| 
     objects.each(&:"calc_#{parent_field}") if self.send("#{child_field}_changed?".to_sym) 
    end 
    end 

    def save!(objects) 
    objects.each { |object| object.save! if object.changed? } 
    end 

    def self.normalize_fields(fields) 
    Hash[fields.collect { |f| f.is_a?(Hash) ? f.to_a : [f, f] }] 
    end 

end 
+0

は便利です:https://github.com/Plinq/big_spoon – apneadiving