DSLs in Ruby

From WhyNotWiki

Jump to: navigation, search

DSLs in Ruby  edit   (Category  edit)


[edit] acts_as_state_machine

Bruce Tate (2007-03-13). Crossing borders: Extensions in Rails: The anatomy of an acts_as plug-in (http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html). Retrieved on 2007-03-14 16:46.

Like many Rails plug-ins, acts_as_state_machine uses a combination of Ruby capabilities and exclusive Rails features to provide not just a library, but also a domain-specific language (DSL), providing a nice experience for users.

...

Using the plug-in, I can decorate my class object directly, using a DSL to represent the various states, transitions between them, and the events that fire those transitions. [...]

class Nonprofit < ActiveRecord::Base

  acts_as_state_machine :initial => :created, :column => 'status'
  
  # These are all of the states for the existing system. 
  state :submitted            
  state :processing           
  state :nonprofit_reviewing  
  state :accepted             
  
  event :accept do
    transitions :from => :processing, :to => :accepted
    transitions :from => :nonprofit_reviewing, :to => :accepted
  end
  
  event :receive do
    transitions :from => :submitted, :to => :processing
  end

  event :send_for_review do   
    transitions :from => :processing, :to => :nonprofit_reviewing
    transitions :from => :nonprofit_reviewing, :to => :processing
    transitions :from => :accepted, :to => :nonprofit_reviewing
  end 

Each transition is a method invocation followed by a hash table. In Ruby, you represent a hash map as key => value pairs, separated by commas, and {enclosed in braces}. When you use a hash map as a function call's last parameter, the braces are optional. You can see that the methods -- state, transition, and event -- combined with closures and hash maps, make a nice DSL.

[edit] Builder templates

http://api.rubyonrails.org/classes/ActionView/Base.html

  xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
    xml.channel do
      xml.title(@feed_title)
      xml.link(@url)
      xml.description "Basecamp: Recent items"
      xml.language "en-us"
      xml.ttl "40"

      for item in @recent_items
        xml.item do
          xml.title(item_title(item))
          xml.description(item_description(item)) if item_description(item)
          xml.pubDate(item_pubDate(item))
          xml.guid(@person.firm.account.url + @recent_items.url(item))
          xml.link(@person.firm.account.url + @recent_items.url(item))

          xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
        end
      end
    end
  end

http://builder.rubyforge.org/

XML Comments are supported …

  xml_markup.comment! "This is a comment"
    #=>  <!-- This is a comment -->

XML processing instructions are supported …

  xml_markup.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
    #=>  <?xml version="1.0" encoding="UTF-8"?>

Nested entity declarations are allowed. For example:

  @xml_markup.declare! :DOCTYPE, :chapter do |x|
    x.declare! :ELEMENT, :chapter, :"(title,para+)"
    x.declare! :ELEMENT, :title, :"(#PCDATA)"
    x.declare! :ELEMENT, :para, :"(#PCDATA)"
  end

  #=>

  <!DOCTYPE chapter [
    <!ELEMENT chapter (title,para+)>
    <!ELEMENT title (#PCDATA)>
    <!ELEMENT para (#PCDATA)>
  ]>
Personal tools