在我的代码库中,我有一堆对象都遵循相同的接口,包括这样的东西:
class MyTestClass
def self.perform(foo, bar)
new(foo, bar).perform
end
def initialize(foo, bar)
@foo = foo
@bar = bar
end
def perform
# DO SOMETHING AND CHANGE THE WORLD
end
end
类之间的区别因素是self.perform
和initialize
的 arity ,以及#perform
类的主体。
所以,我希望能够创建一个ActiveSupport::Concern
(或者只是一个普通的Module
,如果这会更好),这样我就可以做到这样的事情:
class MyTestClass
inputs :foo, :bar
end
然后会使用一些元编程来定义上述方法的self.perform
和initialize
,其中airty将取决于self.inputs
方法指定的airty。
这是我到目前为止所做的:
module Commandable
extend ActiveSupport::Concern
class_methods do
def inputs(*args)
@inputs = args
class_eval %(
class << self
def perform(#{args.join(',')})
new(#{args.join(',')}).perform
end
end
def initialize(#{args.join(',')})
args.each do |arg|
instance_variable_set(@#{arg.to_s}) = arg.to_s
end
end
)
@inputs
end
end
end
这似乎使方法的 arity 正确,但我很难弄清楚如何处理#initialize
方法的主体。
任何人都可以帮我找出一种方法,我可以成功地对#initialize
的主体进行元编程,这样它的行为就像我提供的例子一样吗?
答案 0 :(得分:1)
您可以将其用作#initialize
的正文:
#{args}.each { |arg| instance_variable_set("@\#{arg}", arg) }
但是,我不会将它评估为eval。它通常导致邪恶的事情。也就是说,这是一个实现,它给出了一个不正确的Foo.method(:perform).arity
,但仍然表现得像你期望的那样:
module Commandable
def inputs(*arguments)
define_method(:initialize) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
arguments.zip(parameters).each do |argument, parameter|
instance_variable_set("@#{argument}", parameter)
end
end
define_singleton_method(:perform) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
new(*parameters).perform
end
end
end
class Foo
extend Commandable
inputs :foo, :bar
def perform
[@foo, @bar]
end
end
Foo.perform 1, 2 # => [1, 2]
答案 1 :(得分:1)
你太近了! instance_variable_set
有两个参数,第一个是实例变量,第二个是你想要设置它的值。您还需要获取变量的值,您可以使用send
。
instance_variable_set(@#{arg.to_s}, send(arg.to_s))