我有一个作业问题,无法为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'
您的实现中有几件事需要修正以符合您的期望:
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
(至少对于生产级代码而言)...