Appendix C. A cheat sheet for Ruby’s DSL-friendly features

This appendix will assist you as you become familiar with the DSL-friendly features of Ruby. Please don’t treat this information as a comprehensive language overview. For a more complete and detailed discussion of the language and its syntax, see the references in section C.2.

C.1. DSL-friendly features of Ruby

Ruby is a dynamically typed OO language with strong features of reflective and generative metaprogramming. Ruby’s object model lets you change the behaviors of objects at runtime through reflection into its metamodel. You can also generate code during runtime through metaprogramming, which means that your DSL surface syntax is always concise. Table C.1 gives you a brief overview of the features of Ruby that make it a great language for designing DSLs.

Table C.1. Ruby feature overview
Classes and objects
Ruby is OO. You can define classes that have instance variables and methods. A Ruby object has a set of instance variables and an associated class. A Ruby class is an instance of the class Class. It has everything that an object has, plus a set of method definitions and a reference to the superclass. When you design a DSL using Ruby, it’s common to model the entities of your domain as classes. class Account
def initialize(no, name)
@no = no
@name = name
end
def to_s
"Account no: #{@no} name: #{@name}"
end
end initialize is a special method that’s called when you invoke Account.new. It’s used to set the state of your object after the initial memory allocation. @no and @name set up the instance variables of the class. You don’t have to declare variables before using them.
Singletons
You can define a method specific to a particular object. The set of method definitions within a Ruby class (as I’ve already mentioned) are nothing but singleton methods that are defined for the instance of the class Class. Singletons are also known as class methods in Ruby. accnt = Account.new(12, "john p. ")
def accnt.do_special
##
end
accnt.do_special ## runs
acc = Account.new(23, "peter s. ")
acc.do_special ## error! do_special is a method that’s defined only for the instance accnt. It’s an example of a singleton method in Ruby, where self refers to the instance accnt
Metaprogramming
Metaprogramming is the secret sauce behind Ruby DSLs. Ruby is a reflective language that lets you peek into the runtime meta-objects and change their behaviors. class Account
attr_accessor :no, :name
end attr_accessor is a class method that uses reflective metaprogramming during runtime. It generates accessor methods for the attributes that are supplied as parameters. Note how concise the surface syntax is; the boilerplates are generated during runtime. class Trade < ActiveRecord::Base
has_many :tax_fees
end This example is from the ActiveRecord library of Rails. The one-to-many relation between entities is expressed through a class method in Ruby and used with reflective metaprogramming.
Open classes
Ruby lets you open any class and add or change attributes, methods, and properties during runtime. This feature is popularly known as monkey patching, and is considered to be one of Ruby’s most dangerously powerful features. Because monkey patching works in the global namespace, you need to use this feature judiciously. class Integer
def shares
##
end
end You can design DSL-friendly features using Ruby’s open classes. For example, you can open the class Integer and add a method named shares, so that your DSL user can write code like 2 shares. On the down side, all users who’ll be using the class Integer are affected by this monkey patching. Be careful!
Evals
In Ruby, you can evaluate a string or a block of code on the fly. This is one of the most powerful features of Ruby metaprogramming. In designing DSLs, you can use the evals to set up the appropriate context. Then you can pass a block that invokes methods, without explicitly specifying the context. This makes your DSL syntax less verbose. You can set up a different context in which to evaluate the code by using one of the following flavors of evals that are available:
  • class_eval—evaluate a string or a block of code in the context of a class or a module
  • instance_eval—evaluate a string or a block of code in the context of a class instance
  • eval—evaluate a string or a block of code in the current context
class Account
end
Account.class_eval do
def open
##
end
end The context is the class Account. class_eval creates an instance method for the class Account. Account.instance_eval do
def open
##
end
end The context is the singleton class of self.instance_eval produces a singleton method (or class method) for Account.
Modules
Modules offer a way to group related artifacts like methods, classes, and so on, so that they can be included in your class as a mixin component. Modules also offer a namespace facility in Ruby. module Audit
def record
##
end
end The module defines a new namespace for grouping together all the audit-related methods. Now you can use it as a mixin and include it in your class to make it auditable: class Account
include Audit
## can use the method record here
end
Blocks
Ruby blocks are chunks of code, somewhat similar to anonymous methods, that can be reified for later execution. Just like methods, a Ruby block can take parameters. A block is used synonymously with lambdas, which can implement higher-order functions in Ruby. sum = 0
[1, 2, 3, 4].each do |value|
sum += (value * value)
end
puts sum |value| in horizontal bars is the parameter that gets passed to the block. The method each which belongs to a Ruby array, accepts a block as a parameter
Hash as a variable-length argument list
Ruby has a user-friendly way to implement a variable-length argument list to a method. You can simply pass a hash, then access it as key/value pairs. Doing this adds to the readability of the DSL code and makes implementing the Builder pattern a trivial task. def foo(values)
## values is a hash
end Calling the function: foo(:a => 1, :b => 2) Example application of this idiom from Rails: class Trade
has_many :tax_fees,
:class_name => "TaxFee",
:conditions => "valid_flag = 1",
:order => "name"
end
Duck typing
In Ruby, you design an abstraction NOT based on types, but based on the set of messages it responds to. If an object responds to a quack message, then it’s a duck! This kind of coding wouldn’t work in Java. In Java, you need to specify the type signature of the argument when it’s passed in a method. class Duck
def quack
##
end
end
class DummyDuck
def quack
##
end
end
def check_if_quack(duck)
duck.quack
end The method check_if_quack will work with instances of Duck as well as DummyDuck, because both of them respond to the message quack.

C.2. References

  1. Thomas, Dave, Chad Fowler, and Andy Hunt. 2009. Programming Ruby 1.9: The Pragmatic Programmers’ Guide, Third Edition. The Pragmatic Bookshelf.
  2. Perrotta, Paolo. 2010. Metaprogramming Ruby: Program Like the Ruby Pros. The Pragmatic Bookshelf.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.71.237