我尝试创建一个模块,在其包含的类中设置名为@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
或其他什么。这样做的正确方法是什么?
答案 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