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

它还利用另一个约定来自动正确地包含类和实例方法。ClassMethods如果您在名为and的模块中命名这些方法InstanceMethods(尽管如您所见,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 回答