如何使用通过模块混合的类和实例方法中的类变量

时间:2011-06-02 13:19:46

标签: ruby class-variables

我希望能够将一个选项传递给实例方法可用的类方法(可审计)。我正在使用Module混合类和实例方法。

显而易见的选择是使用类变量,但在尝试访问时遇到错误:

  

未审计的类变量@@ auditable_only_once in Auditable

class Document
  include Auditable
  auditable :only_once => true
end

# The mixin
module Auditable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def auditable(options = {})

      options[:only_once] ||= false

      class_eval do
        # SET THE OPTION HERE!!
        @@auditable_only_once = options[:only_once]
      end
      end
    end

    private

    def audit(action)
      # AND READ IT BACK LATER HERE
      return if @@auditable_only_once && self.audit_item
      AuditItem.create(:auditable => self, :tag => "#{self.class.to_s}_#{action}".downcase, :user => self.student)
    end    
  end

我已经删除了一些代码以使其更容易阅读,完整的代码在这里:https://gist.github.com/1004399(编辑:Gist现在包含解决方案)

1 个答案:

答案 0 :(得分:0)

使用@@类实例变量是不规则的,严格要求的次数极少。大多数时候他们似乎只会造成麻烦或困惑。通常,您可以在类上下文中使用常规实例变量而不会出现问题。

您可能想要做的是为这种事情使用不同的模板。如果您有mattr_accessor,它由ActiveSupport提供,您可能希望使用该变量而不是该变量,或者您始终可以在ClassMethods组件中编写自己的等效项。

我使用的一种方法是将您的扩展分解为两个模块,一个钩子和一个实现。钩子只将方法添加到基类中,如果需要,可以用来添加其余的方法,但不会污染命名空间:

module ClassExtender
  def self.included(base)
    base.send(:extend, self)
  end

  def engage(options = { })
    extend ClassExtenderMethods::ClassMethods
    include ClassExtenderMethods::InstanceMethods

    self.class_extender_options.merge!(options)
  end
end

engage方法可以根据您的喜好调用,例如auditable

接下来,为扩展程序在执行时添加的类和实例方法创建容器模块:

module ClassExtenderMethods
  module ClassMethods
    def class_extender_options
      @class_extender_options ||= {
        :default_false => false
      }
    end
  end

  module InstanceMethods
    def instance_method_example
      :example
    end
  end
end

在这种情况下,有一个简单的方法class_extender_options可用于查询或修改特定类的选项。这避免了必须直接使用实例变量。还添加了一个示例实例方法。

您可以定义一个简单的示例:

class Foo
  include ClassExtender

  engage(:true => true)
end

然后测试它是否正常工作:

Foo.class_extender_options
# => {:default_false=>false, :true=>true}

foo = Foo.new
foo.instance_method_example
# => :example