动态定义Ruby类的Object#initialize

问题描述 投票:2回答:2

在我的代码库中,我有一堆对象都遵循相同的接口,包括这样的东西:

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.performinitialize的arity,以及#perform类的主体。

所以,我希望能够创建一个ActiveSupport::Concern(或者只是一个常规的Module,如果这会更好),这让我做了这样的事情:

class MyTestClass
  inputs :foo, :bar
end

然后将使用一些元编程来定义上述方法的self.performinitialize,其中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

这似乎得到了方法的正确性,但我很难搞清楚如何处理#initialize方法的主体。

任何人都可以帮我找出一种方法,我可以成功地编程#initialize的身体,所以它的行为就像我提供的例子?

ruby-on-rails ruby metaprogramming
2个回答
1
投票

你真是太近了! instance_variable_set有两个参数,第一个是实例变量,第二个是你想要设置它的值。您还需要获取变量的值,您可以使用send来完成。

instance_variable_set(@#{arg.to_s}, send(arg.to_s))

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]
© www.soinside.com 2019 - 2024. All rights reserved.