I'm learning metaprogramming and am trying to make a little DSL to generate HTML. The @result
instance variable is not generating the correct answer because when the h1
method is called, the @result
instance variable is reset. Is there an elegant way to deal with these 'nested' method calls (I know Ruby doesn't technically have nested methods). Here's my code:
class HtmlDsl
attr_reader :result
def initialize(&block)
instance_eval(&block)
end
private
def method_missing(name, *args, &block)
tag = name.to_s
content = args.first
@result = "<#{tag}>#{block_given? ? instance_eval(&block) : content}</#{tag}>"
end
end
html = HtmlDsl.new do
html do
head do
title 'yoyo'
end
body do
h1 'hey'
end
end
end
p html.result # => "<html><body><h1>hey</h1></body></html>"
# desired result # => "<html><head><title>yoyo</title></head><body><h1>hey</h1></body></html>"
Your problem is not that @result
is reset, only that you add into the @result
the return value of instance_eval(&block)
, which is the last line in the block, and not the aggregated block. This should work better (although not perfectly):
class HtmlDsl
attr_reader :result
def initialize(&block)
instance_eval(&block)
end
private
def method_missing(name, *args, &block)
tag = name.to_s
content = args.first
(@result ||= '') << "<#{tag}>"
if block_given?
instance_eval(&block)
else
@result << content
end
@result << "</#{tag}>"
end
end
So now:
html = HtmlDsl.new do
html do
head do
title 'yoyo'
end
body do
h1 'hey'
end
end
end
p html.result
#=> "<html><head><title>yoyo</title></head><body><h1>hey</h1></body></html>"
What I've done is that each call actually renders a fragment to the @result
, so inner calls render inner fragments, each wrapping its own inner fragments with tags.