在模块内设置实例变量

时间:2014-12-22 05:52:59

标签: ruby

我尝试创建一个模块,在其包含的类中设置名为@evaluator的变量和实例变量。然后,我想要在包含的类的method_missing中访问该变量:

module ObjectInquiry
  def self.included(base)
    base.class_eval do

      def method_missing(method, *args, &block)
        @evaluator.call
      end
      def self.inquiry(method_name = nil, &block)
        @evaluator = block if block_given?
      end
    end
  end
end

class Post
  include ObjectInquiry

  inquiry do
    true
  end
end

如果我这样做:

Post.new.awesome?

我得到NoMethodError: undefined method 'call' for nil:NilClass。我猜这是因为我不正确地设置evaluator或其他什么。这样做的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

首先,您的模块结构有点单一。要定义实例方法,我们可以简单地在模块中放置def。要定义类方法,通常会在include钩子中定义嵌套模块ClassMethods并调用base.extend。它节省了一定程度的缩进 - 这似乎并不多,但它可以加起来相当尴尬的代码。以下模块是等效的:

module A
  def self.included(base)
    base.class_eval do
      def instance_method
        puts 'Hello from instance A!'
      end

      def self.class_method
        puts 'Hello from class A!'
      end

      foo() # code evaluated in class body
    end
  end
end

module B
  def self.included(base)
    base.class_eval do
      foo()
    end

    base.extend ClassMethods
  end

  def instance_method
    puts 'Hello from instance B!'
  end

  module ClassMethods
    def class_method
      puts 'Hello from class B!'
    end
  end
end

如果您使用的是Rails或require 'active_support/concern',您甚至可以extend ActiveSupport::Concern减少样板代码的数量:

module C
  extend ActiveSupport::Concern

  included do
    foo()
  end

  def instance_method
    puts 'Hello from instance B!'
  end

  module ClassMethods
    def class_method
      puts 'Hello from class B!'
    end
  end
end

您需要了解的下一件事是实例变量和类实例变量之间的区别。例如,这是如何设置实例变量,然后在实例的上下文中检索它

class C
  def set_value
    @x = 123
  end

  def get_value
    @x
  end
end

这是如何设置一个类实例变量,然后在实例的上下文中检索它。如果您将类D视为类Class的实例,那么术语“类实例变量”就更有意义了。我认为从类的实例访问类实例变量的最简单方法是在类'Eigenclass中定义attr_accessor。

class D
  class << self
    attr_accessor :x
  end

  def self.set_value
    self.x = 123          # self refers to the class, we need to use self.x
  end

  def get_value
    self.class.x          # self refers to the instance, we need to use self.class.x
  end
end

所有这些提示都会产生以下代码:

module ObjectInquiry
  def self.included(base)
    base.class_eval do
      class << self
        attr_accessor :evaluator
      end
    end

    base.extend ClassMethods
  end

  def method_missing(method, *args, &block)
    self.class.evaluator.call
  end

  module ClassMethods
    def inquiry(method_name = nil, &block)
      self.evaluator = block if block_given?
    end
  end
end

测试:

class Post
  include ObjectInquiry

  inquiry do
    true
  end
end

Post.new.flabbergasted?
#=> true