I wanted to do similar things, and ended up with a complex but very helpful class I named DslProxy. It's part of my iron-extensions gem, but you're welcome to pull it out and use it, or take a look at it and see how it works.
The docs for DslProxy are here: http://rubydoc.info/gems/iron-extensions/1.1.2/DslProxy
The github repo is here: https://github.com/irongaze/iron-extensions
Basically, doing this right is hard. As others have noted, instance_eval, which is generally very nice for metaprogramming, loses the calling context/binding, and so you lose your instance variables. Things get even more hairy if you want to nest these builder calls.
Here's a sample of what my DslProxy can do:
class ItemBuilder
def actions(&block)
@actions = []
DslProxy.exec(self, &block)
@actions
end
def item(*args)
@actions << Item.new(*args)
end
end
# ... in your view ...
<%
@times = 5
builder = ItemBuilder.new
builder.actions do
item :foo, link_to(...)
@times.times do
item :bob, link_to(...)
end
end
%>
The calling context is preserved (eg the link_to calls work), the instance vars are propagated (eg @times is available), and the methods that the ItemBuilder instance defines are available without explicit receiver (eg calls to item work as expected).
This, like all metaprogramming, is complex. You might find it helpful to look at the spec for this class here: https://github.com/irongaze/iron-extensions/blob/master/spec/extensions/dsl_proxy_spec.rb
Feel free to contact me with questions, or post issue to my github tracker. :-)