我有很多这样的方法:
def create_machine(name, os_type_id, settings_file='', groups=[], flags={})
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(soap_message))
end
def register_machine(machine)
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(soap_message))
end
它们具有相同的实现但不同数量的不同参数。在几十个类中每个都会有数十种这样的方法。所以我认为我会使用一些元编程来最小化代码重复。
我试图通过define_method
来做这件事,并希望最终得到这样的结果:
vb_method :create_machine, :args => [:name, :os_type_id], :optional_args => [:settings_file, :groups, :flags]
但是我无法找到一种方法将任意数量的命名(非splat)参数传递给define_method
(我认为splat参数会使文档难以实现,也会导致API不方便)
处理这个问题的最佳方法是什么(使用Ruby 2.0)?
UPD 另一种方法是定义方法vb_method:
def vb_method(*vb_meths)
vb_meths.each do |meth|
define_method(meth) do |message={}|
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{meth}".to_sym
VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(message))
end
end
end
然后课程会有这样的电话:
vb_method :create_machine, :register_machine
但是在这种情况下,我需要始终使用hash作为参数调用方法:
machine = vb.create_machine(name: 'my_vm', os_type_id: 'Windows95')
这正是我想要避免的,因为我认为在这种情况下,生成的API无法记录,不便于使用。
答案 0 :(得分:1)
停止尝试避免选项哈希。这是做事的“红宝石方式”。它们并非不可能记录,并且几个主流Ruby库以这种方式使用它们(首先想到的是ActiveRecord和Mysql2)。
请注意,您可以为选项哈希提供默认参数,该服务器作为文档提供,并允许您减少代码重复。
另外,如果可以(以某种方式)将任意数量的命名参数传递给define_method
,请考虑一下代码的工作方式。用户如何记住哪个参数是哪个?他们需要记住以这种方式定义的所有不同方法的所有不同位置参数的顺序和含义。当你有许多类似的方法具有不同含义的参数时,很难保持一切正确。关键字参数(本质上是Ruby的选项哈希值)是专门为避免这种情况而创建的。
如果您担心错误检查,请定义一个帮助方法,检查缺失/无法识别的密钥的选项哈希,并引发一个信息性异常:
def validate_options(known, opts)
opts.each_key { |opt| raise "Unknown option: #{opt}" unless known.include?(opt) }
known.each { |opt, required| raise "Missing required option: #{opt}" if required and not opts.include?(opt) }
end