ruby talk romania

31
A Polyglot world on DSLs

Upload: koen-handekyn

Post on 19-May-2015

650 views

Category:

Documents


0 download

DESCRIPTION

an overview of typical ruby dsl's and a little bit on metaprogramming

TRANSCRIPT

Page 1: Ruby talk romania

A Polyglot worldon DSLs

Page 2: Ruby talk romania

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

Page 3: Ruby talk romania

Why Ruby?

PRODUCTIVITY

Page 4: Ruby talk romania

Why Ruby?

FUN

Page 5: Ruby talk romania

Why Ruby?

Page 6: Ruby talk romania

Why is Ruby productive

‣ Simplicity - next time :) ?

‣ Functional Programming - next time :) ?

‣ DSL’s and Meta-Programming

Page 7: Ruby talk romania

Simplicity

‣ DRY

‣ KISS

‣ YAGNI

‣ Least Surprise

‣ Convention over Configuration (meaningful defaults)

Page 8: Ruby talk romania

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).

Page 9: Ruby talk romania

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

Page 10: Ruby talk romania

ActiveAdmin

Page 11: Ruby talk romania

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

Page 12: Ruby talk romania

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

Page 13: Ruby talk romania

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

Page 14: Ruby talk romania

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

Page 15: Ruby talk romania

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

Page 16: Ruby talk romania

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

Page 17: Ruby talk romania

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

Page 18: Ruby talk romania

Sinatra: A DSL for REST apps

Page 19: Ruby talk romania

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

Page 20: Ruby talk romania

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

Page 21: Ruby talk romania

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

Page 22: Ruby talk romania

SLIM

Page 23: Ruby talk romania

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

Page 24: Ruby talk romania

DSL toolbox

META-PROGRAMMING

*code* that generates *code*

Page 25: Ruby talk romania

But beware

Page 26: Ruby talk romania

But beware

Page 27: Ruby talk romania

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

Page 28: Ruby talk romania

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

Page 29: Ruby talk romania

Class Macro

class << Object def macro(args) puts args endend

class C macro :testend

Page 30: Ruby talk romania

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

Page 31: Ruby talk romania

Q&A