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

发布于 2024-11-11 11:21:13 字数 1009 浏览 6 评论 0原文

我希望能够将传递给我的类方法(可审核)的选项可供实例方法使用。我使用模块混合类和实例方法。

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

Auditable 中未初始化的类变量 @@auditable_only_once

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 现在包含解决方案)

I want to be able to make an option passed to my class method (auditable) available to instance methods. I'm mixing in both the class and instance methods using a Module.

The obvious choice is to use a class variable, but I get an error when trying access it:

uninitialized class variable @@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

I've stripped out some of the code to make this a bit easier to read, the full code is here: https://gist.github.com/1004399 (EDIT: Gist now includes the solution)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

不再见 2024-11-18 11:21:13

使用 @@ 类实例变量是不规则的,并且严格要求使用它们的情况非常罕见。大多数时候,它们似乎只是造成麻烦或混乱。一般来说,您可以在类上下文中使用常规实例变量而不会出现问题。

您可能想要做的是对此类事情使用不同的模板。如果您有由 ActiveSupport 提供的 mattr_accessor,您可能希望使用它来代替该变量,或者您始终可以在 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 方法可以被称为任何你喜欢的方法,正如您的示例中所示,它是可审计的

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

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

Using @@ class instance variables is irregular and the number of of occasions when they're strictly required is exceedingly rare. Most of the time they just seem to cause trouble or confusion. Generally you can use regular instance variables in the class context without issue.

What you might want to do is use a different template for this sort of thing. If you have mattr_accessor, which is provided by ActiveSupport, you may want to use that instead of that variable, or you can always write your own equivalent in your ClassMethods component.

One approach I've used is to break up your extension into two modules, a hook and an implementation. The hook only adds the methods to the base class that can be used to add the rest of the methods if required, but otherwise doesn't pollute the namespace:

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

This engage method can be called anything you like, as in your example it is auditable.

Next you create a container module for the class and instance methods that the extension adds when it is exercised:

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

In this case there is a simple method class_extender_options that can be used to query or modify the options for a particular class. This avoids having to use the instance variable directly. An example instance method is also added.

You can define a simple example:

class Foo
  include ClassExtender

  engage(:true => true)
end

Then test that it is working properly:

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

foo = Foo.new
foo.instance_method_example
# => :example
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文