Ruby DSL with Method Missing
A settings builder can accept names nobody defined in advance. method_missing catches each bare name and turns it into a stored setting.
Any bare name becomes a setting
The Settings class defines no theme or size method. method_missing stores each name in a hash, and a later bare call reads the value back.
class Settings
def initialize
@values = {}
end
def method_missing(name, *args)
if args.length == 1
@values[name] = args[0]
elsif args.empty?
@values[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
@values.key?(name)
end
def to_h
@values
end
end
settings = Settings.new
settings.theme "dark"
settings.size "large"
puts settings.theme
puts settings.sizeOne argument sets, none reads
The argument count splits setters from readers. Reading a name that was never set answers nil, and a call with two arguments falls through to super.
class Settings
def initialize
@values = {}
end
def method_missing(name, *args)
if args.length == 1
@values[name] = args[0]
elsif args.empty?
@values[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
@values.key?(name)
end
def to_h
@values
end
end
settings = Settings.new
settings.theme "dark"
p settings.color
begin
settings.theme "dark", "blue"
rescue => e
puts e.class
endrespond_to? tells the truth
respond_to_missing? checks the stored keys, so respond_to? follows the settings as they appear. The method call then builds a real method for a stored name.
class Settings
def initialize
@values = {}
end
def method_missing(name, *args)
if args.length == 1
@values[name] = args[0]
elsif args.empty?
@values[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
@values.key?(name)
end
def to_h
@values
end
end
settings = Settings.new
puts settings.respond_to? :theme
settings.theme "dark"
puts settings.respond_to? :theme
found = settings.method :theme
puts found.callExample
class Settings
def initialize
@values = {}
end
def method_missing(name, *args)
if args.length == 1
@values[name] = args[0]
elsif args.empty?
@values[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
@values.key?(name)
end
def to_h
@values
end
end
settings = Settings.new
settings.theme "dark"
settings.font "mono"
settings.size 14
p settings.to_h
puts settings.font
puts settings.sizeKey points
- method_missing receives every bare name the builder does not define.
- A call with one argument stores that value under the name.
- A call with no arguments reads the value stored under the name.
- A call with more arguments falls through to super and fails.
- respond_to_missing? turns true for a name once its setting exists.
In this example, three bare calls store three settings in the builder. The p line shows the whole hash of stored values. The last two lines read two settings back by name.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What decides whether a bare call stores a setting or reads one?