A Stackoverflow user suggests studying the Docile source code to learn how to write DSLs in Ruby, but the source code is dense for programmers new to Ruby DSLs. This blog posts demonstrates how to write a DSL that behaves similarly to the Docile gem, but with much less code.
At a fundamental level, Docile evaluates a block in the context of an object with the instance_eval() method:
arr = [] arr.instance_eval do push(1) push(2) reverse! end arr # => [2, 1]
The instance_eval() portion can be abstracted to a method as follows:
def dsl(obj, &block) obj.instance_eval(&block) end arr = [] dsl(arr) do push(1) push(2) pop push(3) end arr # => [1, 3]
The dsl() method takes an object and a block as arguments and simply sends the :instance_eval message to the object with the block as an argument.
Suppose there is a Pizza class to make pizzas. The same dsl() method can be used to customize an instance of the Pizza class.
Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce) obj = Pizza.new dsl(obj) do |pizza| pizza.cheese = true pizza.pepperoni = true pizza.sauce = :extra end obj # => #<struct Pizza cheese=true, pepperoni=true, bacon=nil, sauce=:extra>
The prior example uses a block variable to avoid implicit self syntax that is interpreted by the Ruby interpreter as local variable assignment. If the block variable is omitted, the self keyword is required to clarify that cheese = true is a method call, not local variable assignment.
my_pie = Pizza.new dsl(my_pie) do self.cheese = true self.pepperoni = false self.sauce = :none end my_pie # => #<struct Pizza cheese=true, pepperoni=false, bacon=nil, sauce=:none>
Hi there, great job with this. What the docile gem does is make it possible for you to use *instance variables*, *local variables*, and *function calls* from the context outside of where you call the DSL, as arguments to the DSL functions. For more information, check out the Docile examples.
Thanks for the comment 🙂 If you want to pass arguments to the DSL, can’t you just use instance_exec() instead of instance_eval()? Does instance_exec() provide the functionality of Docile? Thanks!
Ah, sorry for not responding earlier. So, no, and that’s why the Docile gem exists.