如何使用Ruby元编程将回调添加到Rails模型? [英] How do I use Ruby metaprogramming to add callbacks to a Rails model?

查看:38
本文介绍了如何使用Ruby元编程将回调添加到Rails模型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个简单的Cacheable模块,使在父模型中缓存聚合字段变得很简单.该模块要求父对象为每个需要在父级进行缓存的字段实现cacheable方法和calc_方法.

I wrote a simple Cacheable module that makes it simple to cache aggregate fields in a parent model. The module requires that the parent object implement the cacheable method and a calc_ method for each field that requires caching at the parent level.

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模型添加回调.此方法将要求模型实现需要缓存的父模型和字段名称的哈希.

I would like to add callbacks to the ActiveRecord model into which this module is included. This method would require that the model implement a hash of parent models and field names that require caching.

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

如果我使用以下方式手动添加两个回调,则此方法非常有用:

This approach works great if I manually add both callbacks using such as:

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

但是,当我尝试使用cachebacks方法将其添加到回调中时,出现以下错误.

But, I'm receiving the following error when I try to use the cachebacks method to add these to callbacks.

cachebacks(Quote, "*quotes.all")

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

如何将这些回调动态添加到类中?

How do I add these callbacks to the class dynamically?

推荐答案

对于

This looks like a good case for ActiveSupport::Concern. You can tweak your cachebacks method slightly to add it as a class method on the including class:

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!方法中.

The block you pass to cachebacks will be executed in the context of the class that's calling it. In this example, { all } is equivalent to calling Example.all and passing the results into your cache! method.

要在评论中回答您的问题,Concern封装了一个通用模式并在Rails中建立了约定.语法稍微优雅一些​​:

To answer your question in the comments, Concern encapsulates a common pattern and establishes a convention in Rails. The syntax is slightly more elegant:

included do
  # behaviors
end

# instead of

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

它还利用了另一个约定来自动正确地包含类和实例方法.如果您在名为ClassMethodsInstanceMethods的模块中为这些方法命名空间(尽管您已经看到,InstanceMethods是可选的),那么您就完成了.

It also takes advantage of another convention to automatically and correctly include class and instance methods. If you namespace those methods in modules named ClassMethods and InstanceMethods (although as you've seen, InstanceMethods is optional), then you're done.

最后,它处理模块依赖性.该文档提供了一个很好的例子,但是从本质上讲,它避免了include类除了实际需要的模块之外还必须显式包含从属模块.

Last of all, it handles module dependencies. The documentation gives a good example of this, but in essence, it prevents the including class from having to explicitly include dependent modules in addition to the module it's actually interested in.

这篇关于如何使用Ruby元编程将回调添加到Rails模型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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