Rubymonk training
Read the following Rubymonk articles:
- Ruby Primer: Ascent Show archive.org snapshot (archived copy)
- Metaprogramming Ruby Show archive.org snapshot (archived copy)
- Metaprogramming Ruby: Ascent Show archive.org snapshot (archived copy)
For each chapter in each article:
- Play with the introduced Ruby feature using a simple Ruby script or IRB console
- Talk with your mentor what you learned.
Pros and Cons of DSLs
The ability to write your own domain specific language (DSL) can be a very powerful tool. Most notably, it allows you to represent data in a very readable manner. For example, Rails offers a compact DSL Show archive.org snapshot to define new routes.
A big drawback however is that your code often becomes much harder to read. Another con of DSLs is that they tend to be so specific that its users are prevented from using local variables, methods, modules and many other Ruby features. Working around these issues can be quite hacky.
More resources
Exercise: Roll your own DSL
This entire exercise should be implemented using pure Ruby, without any gems.
Write an Addressbook
class that can be used like this:
book = Addressbook.new
book.add_contact 'Henning Koch'
book.add_contact 'Tobias Kraze'
book.contacts # => ['Henning Koch', 'Tobias Kraze']
Now change Addressbook
so contacts can be defined with a custom DSL:
book = Addressbook.parse do
contact 'Henning Koch'
contact 'Tobias Kraze'
end
book.contacts # => ['Henning Koch', 'Tobias Kraze']
Tip
You can use
instance_exec
to run a block on anotherself
.
Now allow each contact to have a phone
and email
attribute:
book = Addressbook.parse do
contact 'Henning Koch' do
phone '12345'
email 'foo@bar.de'
end
contact 'Tobias Kraze' do
phone '67890'
email 'bam@baz.de'
end
end
book.find('Henning Koch') # => { :phone => '12345', :email => 'foo@bar.de' }
book.find('Tobias Kraze') # => { :phone => '67890', :email => 'bam@baz.de' }
Now change Addressbook
so contacts can be accessed by their underscored names:
book.henning_koch # => { :phone => '12345', :email => 'foo@bar.de' }
book.tobias_kraze # => { :phone => '67890', :email => 'bam@baz.de' }
Now change Addressbook
so each contact becomes their own Contact
instance which responds to #phone
and #email
:
book.henning_koch # => Contact<#....>
book.henning_koch.phone # => '12345'
book.henning_koch.email # => 'foo@bar.de'
Now allow arbitrary fields, not just phone
and email
:
book = Addressbook.parse do
contact 'Henning Koch' do
phone '12345'
glasses true
shirt 'red'
end
end
book.henning_koch.shirt # => 'red'
Exercise: DSL styles
The DSL above could also be implemented using this syntax:
book = Addressbook.parse do |ab|
ab.contact 'Henning Koch' do |c|
c.phone '12345'
c.glasses true
c.shirt 'red'
end
end
book.henning_koch.shirt # => 'red'
Change your implementation to work like this.
What are the advantages of this style of DSL? What are the drawbacks? Which do you prefer?
You have probably encountered examples of both styles before. Name a few.
Excercise: Modularity
Consider the following example from the Modularity Show archive.org snapshot README:
# app/models/article.rb
class Article < ActiveRecord::Base
include DoesStripFields[:name, :brand]
end
# app/models/shared/does_strip_fields.rb
module DoesStripFields
as_trait do |*fields|
fields.each do |field|
define_method("#{field}=") do |value|
self[field] = value.strip
end
end
end
end
Go through the Modularity source code and understand how the implementation works. In particular, understand this syntax:
include DoesStripFields[:name, :brand]
What exactly is included here? How does Modularity enable parameterized modules? How do the square brackets work?