ruby talk romania
DESCRIPTION
an overview of typical ruby dsl's and a little bit on metaprogrammingTRANSCRIPT
A Polyglot worldon DSLs
Who Am I
‣ [email protected] 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
Why Ruby?
PRODUCTIVITY
Why Ruby?
FUN
Why Ruby?
Why is Ruby productive
‣ Simplicity - next time :) ?
‣ Functional Programming - next time :) ?
‣ DSL’s and Meta-Programming
Simplicity
‣ DRY
‣ KISS
‣ YAGNI
‣ Least Surprise
‣ Convention over Configuration (meaningful defaults)
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).
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
ActiveAdmin
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
ActiveRecord
‣ Properties are dynamically defined. For every column found in the database, a property is added to the class.
‣ Relations also dynamically add methods for working with the corresponding relations in the ER domain
‣ Scopes express named query fragments in an SQL like DSL
class Client < ActiveRecord::Base # the interesting part is ‘the nothing’ has_one :address has_many :orders has_and_belongs_to_many :roles belongs_to :client
scope :published, where(:published => true) scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
end
Example DSL: rake
‣ An ANT like DSL for expressing tasks and task dependencies between them
‣ rake morning:ready_for_the_day• Turned off alarm. Would have liked 5
more minutes, though.• Brushed teeth.• Showered.• Shaved.• Styled hair.• Made 2 cups of coffee. Shakes are gone.• Dog walked.• Ready for the day!
namespace :morning do desc "Turn off alarm." task :turn_off_alarm do puts "Turned off alarm. Would have liked 5 more minutes, though." end
desc "Take care of normal hygiene tasks." task :groom_myself do puts "Brushed teeth." puts "Showered." puts "Shaved." end desc "Make coffee" task :make_coffee do cups = ENV["COFFEE_CUPS"] || 2 puts "Made #{cups} cups of coffee. Shakes are gone." end
desc "Walk the dog" task :walk_dog do puts "Dog walked." end
desc "Get ready for the day" task :ready_for_the_day => [:turn_off_alarm, :groom_myself, ...] do puts "Ready for the day!" endend
Example DSL: rails routes
‣ The Rails router recognizes URLs and dispatches them to a controller’s action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views with shortcuts for REST-full resource
match "/patients/:id" => "patients#show"
resources :photos
namespace :admin do resources :posts, :commentsend
resources :photos
• GET /photos => index• GET /photos/new => new• POST /photos => create• GET /photos/:id => show• GET /photos/:id/edit => edit• 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
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
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
Workflow
‣ Workflow is a finite-state-machine-inspired API for modeling and interacting with what we tend to refer to as ‘workflow’.
‣ A lot of business modeling tends to involve workflow-like concepts, and the aim of this library is to make the expression of these concepts as clear as possible, using similar terminology as found in state machine theory.
‣ http://www.geekq.net/workflow/
class Article include Workflow workflow do state :new do event :submit, :transitions_to => :awaiting_review end state :awaiting_review do event :review, :transitions_to => :being_reviewed end state :being_reviewed do event :accept, :transitions_to => :accepted event :reject, :transitions_to => :rejected end state :accepted state :rejected endend
Sinatra: A DSL for REST apps
CanCan
‣ CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access.
‣ Define abilities• can :manage, Article
‣ Check abilities• cannot? :destroy, @article
• authorize! :read, @article
class Ability
include CanCan::Ability
def initialize(user) if user.admin?
# user can perform any action on any object
can :manage, :all# user can perform any action on the articlecan :manage, Article # negativecannot :destroy, Project # conditionscan :read, Project, :category => { :visible => true }
else # user can read any object
can :read, :all end endend
cannot? :destroy, @articleauthorize! :read, @article
RSpec
‣ RSpec is testing tool for the Ruby programming language. Born under the banner of Behaviour-Driven Development.
require 'bowling'
describe Bowling, "#score" do it "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should eq(0) endend
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 endend
Cucumber
‣ Cucumber is a tool that executes plain-text functional descriptions as automated tests. The language that Cucumber understands is called Gherkin (external DSL)
‣ Applicable for BDD for Ruby, Java, C#, etc
Feature: Search courses In order to ensure better utilization of courses Potential students should be able to search for courses
Scenario: Search by topic Given there are 240 courses which do not have the topic "biology" And there are 2 courses A001, B205 that each have "biology" as one of the topics When I search for "biology" Then I should see the following courses: | Course code | | A001 | | B205 |
IMPLEMENTING phrases (ruby example)
Given /there are (\d+) coffees left in the machine/ do |n| @machine = Machine.new(n.to_i)end
SLIM
The Future is to the Polyglot
I thank God that I speak in tongues more than all of you (1 Corinthians 14:18)
HTML CSS Javascript
SLIM SASSCoffeescript
JQuery
RubyActiv
eCanC
anWorkflwo
Routes
...
RakeGemfile
Puppet
RSpec
Cucumber
...
SQL REDIS MongoDB
Client
Client Source
Server(Business
Layer)
PersistenceLayer
Infrastructure
DSL toolbox
META-PROGRAMMING
*code* that generates *code*
But beware
But beware
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
Properties :)
‣ monkey patch Object
‣ yield self from constructor
class Developer properties :level, :name, :surname
def initialize yield ( self ) if block_given? endend
# Test our Setterssuperduperdev = Developer.new do |d| d.level = "journeyman" d.name = "Edmore" d.surname = "Moyo"end
anotherdev = Developer.new(name: "koen", surname: "handekyn", level: "ceo")
class << Objectdef property( *names )
" names.each do |name|" define_method( "#{name}=" ) do |value|" instance_variable_set( "@#{name}", value )" end define_method( name ) do" eval("@#{name}") end end end alias_method :properties, :property" end
Class Macro
class << Object def macro(args) puts args endend
class C macro :testend
Hooks
$INHERITORS = []
class Animal def self.inherited(subclass) $INHERITORS << subclass endend
class Fish < Animal def a() endendclass Dog < Animal def b() endendclass Cat < Animal def c() endend
puts $INHERITORS.map { |c| c.name }.sort
# Cat# Dog# Fish
Q&A