I have a homework problem to create a simple DSL configuration for Ruby.
The problem is in method_missing. I need to print out values of keys, but they're printing out automaticaly, not by command.
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's init.rb output:
marie@marie:~/dsl$ ruby init.rb
production
value1
value2
:key3=
["value3"]
:key4=
["value4"]
The problem is that the values are printing automatically, I need to print them out using:
config.key1 => 'value1'
config.key2 => 'value2'
config::Group1.key3 => 'value3'
config::Group1.key4 => 'value4'
There are several things in your implementation that need to be fixed to match your expectations:
1)
configclass method returns the result of the block execution, so in your example theconfigvariable containsConfigus::Group1, notConfigusas you probably expect.2)
method_missingnow behaves in the very same way regardless of the method name. But it is quite clear that you expect different behavior for setters and getters.So a naive (and dirty) fix could look like the following:
(the same applies to the
Group1, but I believe you got the idea of how to fix it too)There is one more practical problem with your DSL, though: the support for nested setting is hard-coded and this makes it non-flexible. You cannot build nested hierarchies this way, for example, and to introduce new nested group you have to change the class definition (add method(s)). There are plenty of ways to fix this in Ruby, for example, we could use OpenStruct that does a lot of method_missing magic under the hood and simplifies the code a bit because of that. Dirty example:
Now you can nest the settings, for example
and then
P.S. One more thing to mention: the rule of thumb is to define the appropriate
respond_to_missing?each time you play withmethod_missing(at least for production-grade code)...