如何在Ruby中使用method_missing

问题描述 投票:0回答:1

我有一个作业问题,无法为Ruby创建简单的DSL配置。

问题出在method_missing中。我需要打印出键的值,但是它们是自动打印出来的,而不是通过命令打印出来的。

init.rb:

require_relative "/home/marie/dsl/store_application.rb"

config = Configus.config do |app|
  app.environment = :production

  app.key1 = "value1"
  app.key2 = "value2"

  app.group1 do |group1|
    group1.key3 = "value3"
    group1.key4 = "value4"
  end
end

store_application.rb:

class Configus
  class << self
    def config
      yield(self)
    end

    #    attr_accessor :environment,
    #                  :key1,
    #                  :key2,
    #                  :key3,
    #                  :key4

    def method_missing(m, args)
      puts args
    end

    def group1(&block)
      @group1 ||= Group1.new(&block)
    end
  end

  class Group1
    class << self
      def new
        unless @instance
          yield(self)
        end
        @instance ||= self
      end

      # attr_accessor :key1,
      #               :key2,
      #               :key3,
      #               :key4

      def method_missing(m, *args)
        p m, args
      end
    end
  end
end

Ruby的init.rb输出:

marie@marie:~/dsl$ ruby init.rb 
production
value1
value2
:key3=
["value3"]
:key4=
["value4"]

问题是值是自动打印的,我需要使用以下命令将它们打印出来:

config.key1         => 'value1'
config.key2         => 'value2'
config::Group1.key3 => 'value3'
config::Group1.key4 => 'value4'
ruby dsl method-missing
1个回答
0
投票

您的实现中有几件事需要修正以符合您的期望:

1] config类方法返回块执行的结果,因此在您的示例中,config变量包含Configus::Group1,而不是您可能期望的Configus

2)method_missing现在的行为与方法名称完全相同。但很显然,您期望设置器和获取器的行为不同。

因此,一个幼稚的(肮脏的)修复可能如下所示:

class Configus
  class << self    
    def config
      yield(self) if block_given?
      self
    end

    def method_missing(method_name, *args)
      @config_keys ||= {}

      if method_name.to_s.end_with?('=')
        @config_keys[method_name.to_s[0..-2].to_sym] = args
      elsif @config_keys.key?(method_name)
        @config_keys[method_name]
      else
        super
      end
    end

    # ...
  end

  # ...
end

([Group1同样适用,但我相信您也知道如何解决它]

但是,DSL还有一个更实际的问题:对嵌套设置的支持是硬编码的,这使其变得不灵活。例如,您不能以这种方式构建嵌套层次结构,并且要引入新的嵌套组,您必须更改类定义(添加方法)。有很多方法可以在Ruby中解决此问题,例如,我们可以使用OpenStruct,它可以在幕后进行很多method_missing魔术操作,并因此简化了代码。肮脏的例子:

require "singleton"

class Configus
  include Singleton

  class ParamSet < OpenStruct
    def method_missing(method_name, *args)
      # Naive, non-robust support for nested groups of settings
      if block_given?
        subgroup = self[method_name] || ParamSet.new
        yield(subgroup)
        self[method_name] = subgroup
      else
        super
      end
    end
  end

  def self.config
    yield(self.instance.config) if block_given?
    self.instance
  end

  def method_missing(method_name, *args)
    config.send(method_name, *args) || super
  end

  def config
    @config ||= ParamSet.new
  end
end

现在,您可以嵌套设置,例如

config = Configus.config do |app|
  app.environment = :production

  app.key1 = "value1"
  app.key2 = "value2"

  app.group1 do |group1|
    group1.key3 = "value3"
    group1.key4 = "value4"
    group1.group2 do |group2|
      group2.key5 = "foo"
    end
  end
end

然后

config.key1 #=> "value1"
config.group1.key3 #=> "value3"
config.group1.group2.key5 #=> "foo"

P.S。还有一件事要提:经验法则是每次使用respond_to_missing?时都定义适当的method_missing(至少对于生产级代码而言)...

© www.soinside.com 2019 - 2024. All rights reserved.