The document discusses domain specific languages (DSLs) and their usage in Ruby. It provides examples of internal and external DSLs and how they are used to express concepts in a domain-focused notation that is clear and unambiguous. Specific DSLs mentioned include ActiveAdmin, ActiveRecord, Rails routes, Rake, Capistrano, Slop, Workflow, CanCan, RSpec, Cucumber, and Slim. The document advocates that DSLs allow programmers to use high-level concepts that are translated to lower-level implementations.
2. Who Am I
‣ koen.handekyn@up-nxt.com
CEO UP-nxt, R&D division of the UnifiedPost Group.
Electronic Document Handling (Archiving, Legal
Delivery, eInvoicing, Payment, Workflow)
‣ Education: Computer Science / Business
Administration
‣ Background: Telecommunications
6. Why is Ruby productive
‣ Simplicity - next time :) ?
‣ Functional Programming - next time :) ?
‣ DSL’s and Meta-Programming
7. Simplicity
‣ DRY
‣ KISS
‣ YAGNI
‣ Least Surprise
‣ Convention over Configuration (meaningful defaults)
8. DSL (domain specific languages)
‣ The DSL does not solve every problem in the
universe but rather focuses on one little
domain
• such as building software, querying data, or constructing UIs
‣ Notation conforms to meaning
• if you read a sentence (statement) in the DSL, you have a clear, unambiguous idea
of what it does.
‣ A DSL uses high-level concepts that the
programmer can use, and translates those to a lower-
level implementation (internally).
9. Internal vs External
‣ An external DSL has it’s own custom scanner and
parser or uses a standard one, like XML. example DSL’s :
mvn, ANT, SQL, dot, regexp, CSS.
‣ An internal DSL uses a GPL with meta-programming
facilities and is hence embedded inside a GPL like Ruby,
LISP, Scala. This approach has recently been popularized by
the Ruby community. Aka FluentInterfaces
‣ http://martinfowler.com/bliki/DomainSpecificLanguage.html
11. ActiveAdmin.register Product do
# Create sections on the index screen
scope :all, :default => true
scope :available
scope :drafts
# Filterable attributes on the index screen
filter :title
filter :author, :as => :select, :collection => lambda{ Product.authors }
filter :price
filter :created_at
# Customize columns displayed on the index screen in the table
index do
column :title
column "Price", :sortable => :price do |product|
number_to_currency product.price
end
default_actions
end
end
12. ActiveRecord
‣ Properties are dynamically
defined. For every column
found in the database, a class Client < ActiveRecord::Base
property is added to the # the interesting part is ‘the nothing’
class. has_one :address
has_many :orders
‣ Relations also dynamically
has_and_belongs_to_many :roles
belongs_to :client
add methods for working scope :published, where(:published => true)
scope :published_and_commented,
with the corresponding published.and(self.arel_table[:comments_count].gt(0))
relations in the ER domain end
‣ Scopes express named
query fragments in an
SQL like DSL
13. Example DSL: rake
namespace :morning do
desc "Turn off alarm."
‣
task :turn_off_alarm do
An ANT like DSL for puts "Turned off alarm. Would have liked 5 more
minutes, though."
expressing tasks and task end
dependencies between desc "Take care of normal hygiene tasks."
task :groom_myself do
them
puts "Brushed teeth."
puts "Showered."
puts "Shaved."
‣
end
rake desc "Make coffee"
morning:ready_for_the_day task :make_coffee do
cups = ENV["COFFEE_CUPS"] || 2
puts "Made #{cups} cups of coffee. Shakes are gone."
• Turned off alarm. Would have liked 5 end
more minutes, though.
• Brushed teeth. desc "Walk the dog"
task :walk_dog do
• Showered. puts "Dog walked."
• Shaved. end
• Styled hair. desc "Get ready for the day"
• Made 2 cups of coffee. Shakes are gone. task :ready_for_the_day =>
• Dog walked. [:turn_off_alarm, :groom_myself, ...] do
• Ready for the day! puts "Ready for the day!"
end
end
14. Example DSL: rails routes
‣ The Rails router match "/patients/:id" => "patients#show"
recognizes URLs and resources :photos
dispatches them to a namespace :admin do
controller’s action. It can resources :posts, :comments
end
also generate paths and
URLs, avoiding the need resources :photos
to hardcode strings in •
•
GET /photos => index
GET /photos/new => new
• POST /photos => create
your views with shortcuts •
•
GET /photos/:id => show
GET /photos/:id/edit => edit
for REST-full resource •
•
POST /photos/:id => update
DELETE /photos/:id => destroy
• photos_path|url => /photos
• new_photo_path|url => photos/new
• edit_photo_path|url(:id) => photos/:id/edit
• photo_path|url(:id) => photos/:id
15. Capistrino
‣ Capistrano is a utility and framework for executing
commands in parallel on multiple remote machines, via SSH.
‣ http://en.wikipedia.org/wiki/Capistrano
task :xml_libs, :hosts => "www.capify.org"
do
run "ls -x1 /usr/lib | grep -i xml"
end
16. Slop
‣ Slop is a simple option parser with an easy to remember
syntax and friendly API.
‣ https://github.com/injekt/slop
opts = Slop.parse do
banner "ruby foo.rb [options]n"
on :name=, 'Your name'
on :p, :password, 'Your password', :argument => :optional
on :v, :verbose, 'Enable verbose mode'
end
17. Workflow
class Article
‣ Workflow is a finite-state- include Workflow
workflow do
machine-inspired API for state :new do
modeling and interacting with event :submit, :transitions_to
what we tend to refer to as => :awaiting_review
end
‘workflow’. state :awaiting_review do
‣ A lot of business modeling event :review, :transitions_to
=> :being_reviewed
tends to involve workflow-like end
concepts, and the aim of this state :being_reviewed do
event :accept, :transitions_to
library is to make the => :accepted
expression of these concepts event :reject, :transitions_to
as clear as possible, using => :rejected
end
similar terminology as found in state :accepted
state machine theory. state :rejected
‣ http://www.geekq.net/
end
end
workflow/
19. CanCan
class Ability
‣ CanCan is an
include CanCan::Ability
def initialize(user)
authorization library for if user.admin?
# user can perform any action on
Ruby on Rails which any object
can :manage, :all
restricts what resources # user can perform any action on
a given user is allowed to
the article
can :manage, Article
access. # negative
cannot :destroy, Project
# conditions
‣ Define abilities can :read, Project, :category =>
{
:visible => true
• can :manage, Article }
else
‣ Check abilities # user can read any object
can :read, :all
end
• cannot? :destroy, @article end
end
• authorize! :read, @article
cannot? :destroy, @article
authorize! :read, @article
20. RSpec
‣ RSpec is testing tool for the require 'bowling'
Ruby programming language. describe Bowling, "#score" do
Born under the banner of it "returns 0 for all gutter game" do
bowling = Bowling.new
Behaviour-Driven 20.times { bowling.hit(0) }
bowling.score.should eq(0)
end
Development. end
describe Order do
context "with no items" do
it "behaves one way" do
# ...
end
end
context "with one item" do
it "behaves another way" do
# ...
end
end
end
21. Cucumber
Feature: Search courses
‣ Cucumber is a tool that In order to ensure better utilization of
courses
Potential students should be able to
executes plain-text search
for courses
functional descriptions as Scenario: Search by topic
automated tests. The Given there are 240 courses which
do not have the topic "biology"
And there are 2 courses A001, B205
language that Cucumber that each have "biology" as one of
the topics
understands is called When I search for "biology"
Then I should see the following
Gherkin (external DSL) courses:
| Course code |
| A001 |
‣ Applicable for BDD for | B205 |
Ruby, Java, C#, etc IMPLEMENTING phrases (ruby example)
Given /there are (d+) coffees left in the
machine/ do |n|
@machine = Machine.new(n.to_i)
end
23. The Future is to the Polyglot
Client HTML CSS Javascript
Client Coffeescript
SLIM SASS
Source JQuery
Server
Activ CanC Work Rout
(Business Ruby ...
e an flwo es
Layer)
Persistence SQL REDIS MongoDB
Layer
Infrastructur Gem Pupp RSpe Cucu
Rake ...
e file et c mber
I thank God that I speak in tongues more than all of you (1 Corinthians 14:18)
24. DSL toolbox
META-
PROGRAMMING
*code* that generates *code*
27. Swords, Guns, and other toys
‣ symbols. These have less line-noise than strings and tend to be favored by DSL writers.
‣ procs. More than anything else, these make DSL’s in Ruby read and work naturally. They
allow simple encapsulation of functionality (so you can write augmented branching
constructs), and also let you do delayed evaluation of code.
‣ modules. With modules you can easily specialize individual objects with DSL methods.
‣ eval, instance_eval, and class_eval. It is definitely worth learning the difference
between these three, and how they can be used. These are critical to many different
dynamic techniques.
‣ define_method. This lets you define new methods that can reference their closure,
which you can’t do so easily using the eval methods.
‣ alias_method. Rails uses this to good effect to allow modules to override behavior of
the classes they are included in.
‣ Module#included lets you do additional processing at the moment that a module is
included in a class.
‣ Class#inherited lets you keep track of who is inheriting from what
28. Properties :)
‣ monkey patch Object class Developer
properties :level, :name, :surname
‣ yield self from def initialize
yield ( self ) if block_given?
constructor end
end
# Test our Setters
superduperdev = Developer.new do |d|
class << Object d.level = "journeyman"
def property( *names ) d.name = "Edmore"
" names.each do |name| d.surname = "Moyo"
" define_method( "#{name}=" ) do |value| end
" instance_variable_set( "@#{name}", value )
" end anotherdev = Developer.new(name: "koen",
define_method( name ) do" surname: "handekyn",
eval("@#{name}") level: "ceo")
end
end
end
alias_method :properties, :property"
end
29. Class Macro
class << Object
def macro(args)
puts args
end
end
class C
macro :test
end
30. Hooks
$INHERITORS = []
class Animal
def self.inherited(subclass)
$INHERITORS << subclass
end
end
class Fish < Animal
def a() end
end
class Dog < Animal
def b() end
end
class Cat < Animal
def c() end
end
puts $INHERITORS.map { |c| c.name }.sort
# Cat
# Dog
# Fish
Typically, this approach is used in conjunction with instance_eval and friends, so that some configuration file is loaded (or a block is given) and executed within the context of the sandbox. (This sounds similar to the top-level methods technique, with the exception that theDSL is restricted to the sandbox&#x2014;there no global methods involved.) \n Capistrano and Needle both use this approach\n