【问题标题】:Dynamically defining a Object#initialize for a Ruby class为 Ruby 类动态定义 Object#initialize
【发布时间】:2018-03-06 15:03:19
【问题描述】:

在我的代码库中,我有一堆对象,它们都遵循同一个接口,其中包含如下内容:

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.performinitializearity,加上#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

这似乎使方法的 arity 正确,但我很难弄清楚如何处理 #initialize 方法的主体。

谁能帮我找出一种方法,让我可以成功地对#initialize 的主体进行元编程,使其表现得像我提供的示例一样?

【问题讨论】:

    标签: ruby-on-rails ruby metaprogramming


    【解决方案1】:

    您可以将其用作#initialize 的正文:

    #{args}.each { |arg| instance_variable_set("@\#{arg}", arg) }
    

    但是,我不会对它进行字符串评估。它通常会导致邪恶的事情。也就是说,这里的实现给出了一个不正确的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]
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        猜你喜欢
        • 2011-09-28
        • 1970-01-01
        • 2016-09-06
        • 2011-07-04
        • 2013-02-02
        • 1970-01-01
        • 2018-09-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多