9

親モデルの集計フィールドを簡単にキャッシュできるようにする単純な Cacheable モジュールを作成しました。このモジュールでは、親オブジェクトがcacheableメソッドとcalc_、親レベルでのキャッシュを必要とする各フィールドのメソッドを実装する必要があります。

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>

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

4

3 に答える 3

7

これは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 で規則を確立します。構文はもう少し洗練されています。

included do
  # behaviors
end

# instead of

def self.included(base)
  base.class_eval do
    # behaviors
  end
end

また、別の規約を利用して、クラス メソッドとインスタンス メソッドを自動的かつ正確にインクルードします。andという名前のモジュールでこれらのメソッドを名前空間化するClassMethodsInstanceMethods(ご覧のようにInstanceMethods省略可能ですが)、完了です。

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

于 2012-08-23T04:08:28.223 に答える
1

ソリューションを書くのに役立った答えをくれた Brandon に感謝します。

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

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

このモジュールは ActiveSupport::Concern を拡張し、コールバックを追加してキャッシュを実行します。親クラスは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
于 2012-08-23T04:51:24.083 に答える
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
于 2012-08-23T03:46:48.333 に答える