Posted over 4 years ago. Visible to the public.

Advanced Ruby: Metaprogramming and DSLs [3d]

Rubymonk training

Read the following Rubymonk articles:

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.

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:

Copy
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:

Copy
book = Addressbook.parse do contact 'Henning Koch' contact 'Tobias Kraze' end book.contacts # => ['Henning Koch', 'Tobias Kraze']

Now allow each contact to have a phone and email attribute:

Copy
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:

Copy
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:

Copy
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:

Copy
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:

Copy
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 README:

Copy
# 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:

Copy
include DoesStripFields[:name, :brand]

What exactly is included here? How does Modularity enable parameterized modules? How do the square brackets work?

Owner of this card:

Avatar
Henning Koch
Last edit:
about 1 month ago
by Henning Koch
Posted by Henning Koch to makandra Curriculum
This website uses cookies to improve usability and analyze traffic.
Accept or learn more