在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类的实例。