get '/hi'do 'Hello world!' end
For another example, check out wingtips a DSL for writing presentation slides:
slide 'Why' do fullscreen_image 'images/_why.png' para '_why', left: 100, top: 100 end
As you can see above, often DSLs involve adding top-level methods for convenience. Methods like
slide above that are available outside of a class definition whatsoever, just like your standard ruby
puts. These methods are often used as an entry point into the DSL, internally create a new object and then
instance_eval the block or something like that. Unfortunately many times developers monkey patch
Object to reach their goal, to make this methods available. That is totally unnecessary! We can write beautiful DSLs, like the above, without polluting every Ruby object ever with our methods.
I decided to share this method/article, as I noticed that this seems to be rather unknown even to a lot of experienced developers.
The traditional way: monkey patching Object
Let’s first have a look at the “traditional way” to implement DSLs. The traditional way is to use Ruby’s open class system to define the method straight on the
Object class, making it available everywhere:
class Object private def dsl_method puts 'Nice dsl you got' end end dsl_method # => 'Nice dsl you got'
This works. However, now there is a private method
dsl_method defined on every object (as almost all objects inherit from
Object). Even worse, as modules and classes are also objects, the
dsl_method is also defined on them. That’s not an ideal situation.
Object.new.send :dsl_method # => 'Nice dsl you got' module MyModule end MyModule.send :dsl_method # => 'Nice dsl you got'
What about def dsl_method?
That unfortunately creates much the same problems as the monkeypatching Object approach, described above:
def dsl_method puts "Nice dsl you got" end dsl_method # => Nice dsl you got Object.send :dsl_method # => Nice dsl you got Object.new.send :dsl_method # => Nice dsl you got
Main Object to the rescue!
You probably know that in ruby you can call methods without an explicit receiver, if the receiver is
self is depends on the context. But what is
self at the top level of a ruby script? Let’s check:
p self # => main p self.class # => Object
main object, an instance of the
Object class. How about we only add our DSL methods to that one object instead of a whole class hierarchy? How can we do that? Well we could use
define_singleton_method, but I’m more a fan of defining a module and then calling
extend on the main object. Let’s see how that plays out:
module DSL def dsl_method puts 'Nice dsl you got' end end extend DSL dsl_method # => 'Nice dsl you got'
That works nicely. The method is only defined on the main object, not unnecessarily polluting objects with methods. Other instances of
Object know nothing about it, when asked for that method they throw a
Object.send :dsl_method # => NoMethodError
Great! But it would have worked just the same with
define_singleton_method, so why do I prefer the module based approach? Reusability! Imagine you want to use the DSL inside of other classes or with other objects. By nature all of that is possible with the old “monkeypatch” approach, as every object just has that method defined. With the module based approach we can introduce our DSL methods into inheritance hierarchies in a much more fine-grained way.
class WantDSL include DSL end WantDSL.new.dsl_method # => 'Nice dsl you got'
Do projects actually use this?
The aforementioned Sinatra uses this approach since shortly after eurucamp 2012 I believe. The main object is extended here with the methods defined here. It’s working fine and in production at many companies ;)
I use this approach in some of my projects. We use it in Shoes 4 to make our “built-in methods” available both at the top-level and inside an app, as there are some methods that are available both outside and inside of the app scope such as
alert. Lastly we use it in wingtips to make the slide declaration DSL available at the top-level. You can also check out the pull request, that introduced the approach described here at wingtips and moved away from yet another approach, which was instance_evaling the code of ruby files.
Anyway, I hope you find this blog post useful in your endeavours to create beautiful DSLs without polluting other objects :)
Edit/Update: Added some clarification, to show that this proposed method is just to be used for the “entry point” to the DSL, not for the whole DSL. Also added a section showing that using
def is basically the same here as monkeypatching
Object. Thanks for the feedback on reddit.