在Ruby模块中动态创建访问器
我在Ruby中有一个Config模块,我希望能够添加任意变量。 我使用method_missing和instance_variable_set创建了它,如下所示:
module Conf #add arbitrary methods to config array def self.method_missing(m, *args) args = args.pop if args.length==1 instance_variable_set("@#{m}", args) end end
但是,我在动态创建访问器时遇到了麻烦。 当我尝试使用attr_accessor时如下:
module Conf #add arbitrary methods to config array def self.method_missing(m, *args) args = args.pop if args.length==1 instance_variable_set("@#{m}", args) module_eval("attr_accessor :#{m}") end end
我得到以下内容:
Conf :: s3_key(’1234ABC’)#Conf :: s3_key = nil
如果我尝试单独创建访问器:
module Conf #add arbitrary methods to config array def self.method_missing(m, *args) args = args.pop if args.length==1 instance_variable_set("@#{m}", args) module_eval("def self.#{m};@#{m};end") module_eval("def self.#{m}=(val);@#{m}=val;end") end end
发生以下情况:
Conf::s3_key('1234ABC') # Conf::s3_key='1234ABC' - correct
但如果我试图覆盖该值,我会收到错误
Conf::s3_key('1234ABC') # ok Conf::s3_key('4567DEF') #(eval):1:in `s3_key': wrong number of arguments (1 for 0) (ArgumentError)
我究竟做错了什么?
首先,即使正常描述, attr_accessor
也无法用于Module。
module Conf attr_accessor :s3_key end
其次,覆盖错误是因为method_missing
只执行一次
def self.method_missing(m, *args) #: instance_variable_set("@#{m}", args) module_eval("def self.#{m};@#{m};end") # <- method defined
该方法在第一次调用中定义。 参数的数量为0
Conf::s3_key('1234ABC') # call method_missing Conf::s3_key('4567DEF') # call self.s3_key()
例如,这样怎么样:
module Conf def self.method_missing(m, *args) args = args.pop if args.length==1 instance_variable_set("@#{m}", args) module_eval(<
要么
module Conf def self.method_missing(m, *args) if (m.to_s =~ /^(.+)=$/) args = args.pop if args.length==1 instance_variable_set("@#{$1}", args) else instance_variable_get("@#{m}") end end end Conf::s3_key = 'foo' Conf::s3_key = 'bar' p Conf::s3_key # "bar"
您只需要更改代码的一行。
码
module Conf def self.method_missing(m, *args) args = args.pop if args.length==1 instance_variable_set("@#{m}", args) Module.instance_eval("attr_accessor :#{m}") end end
例
Conf.s3_key('1234ABC') Conf.s3_key #=> "1234ABC" Conf.s3_key = '4567DEF' Conf.s3_key #=> "4567DEF"
(或Conf::s3_key('1234ABC')
等)
说明
为类定义访问器并应用于类实例。 在这种情况下,类实例是模块Conf
,因此必须为Conf
是实例的类定义attr_accessor
:
Conf.class #=> Module
注意
Module.is_a? Class #=> true Conf.instance_of? Module #=> true
我们通过在Module
上调用BasicObject#instance_eval来完成此操作。 我们需要使用instance_eval
以便在调用变量m
时它将在范围内。
最后一个观察。 假设模块Conf
被另一个模块M
包围。 然后代码仍然有效:
M::Conf.s3_key('1234ABC') M::Conf.s3_key #=> "1234ABC" M::Conf.s3_key = '4567DEF' M::Conf.s3_key #=> "4567DEF"
这是因为所有模块(包括嵌套模块)都是Module
类的实例。