Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Your Own Ruby DSL - Tropical Ruby 2015

Writing Your Own Ruby DSL - Tropical Ruby 2015

In this talk we'll go through some concepts on the Ruby language that will help us creating our own custom Domain Specific Language. It's a very hands-on talk. We'll play with some Ruby concepts like dynamic methods, blocks, procs and self, but our main goal is to write (and learn how) our own Pizza Cookbook DSL

Ricardo Nacif

March 06, 2015
Tweet

Other Decks in Technology

Transcript

  1. UAI

  2. DSL

  3. driver = Selenium::WebDriver.for :chrome driver.navigate.to "google.com" input = driver.find_element(:name, 'q')

    input.send_keys('ruby') button = driver.find_element(:name, 'lst-ib') button.click #capybara visit 'google.com' fill_in 'Search', with: 'ruby' click_button 'Google Search'
  4. driver = Selenium::WebDriver.for :chrome driver.navigate.to "google.com" input = driver.find_element(:name, 'q')

    input.send_keys('ruby') button = driver.find_element(:name, 'lst-ib') button.click #capybara visit 'google.com' fill_in 'Search', with: 'ruby' click_button 'Google Search'
  5. Gherkin (Cucumber) Feature: Serve coffee Coffee should not be served

    until paid for Coffee should not be served until the button has been pressed If there is no coffee left then money should be refunded Scenario: Buy last coffee Given there are 1 coffees left in the machine And I have deposited 1$ When I press the coffee button Then I should be served a coffee
  6. RSpec # these two expectations... expect(alphabet).to start_with("a") expect(alphabet).to end_with("z") #

    ...can be combined into one expression: expect(alphabet).to start_with("a").and end_with( "z")
  7. Factory Girl FactoryGirl.define do factory :user do first_name "John" last_name

    "Doe" admin false end factory :admin, class: User do first_name "Admin" last_name "User" admin true end end
  8. class User attr_accessor :name, :age def initialize name, age @name

    = name @age = age end end User = Struct.new(:name, :age) struct
  9. Pizza = Struct.new(:name, :vegetable, :cheese, :sauce, :topping) def add_vegetable name

    @pizza.vegetable = name end #def add_sauce, add_cheese... def create_pizza name @pizza = Pizza.new(name) yield if block_given? end new_pizza 'Frango Catupiry' do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry' add_topping 'chicken' end
  10. Pizza = Struct.new(:name, :vegetable, :cheese, :sauce, :topping) def add_vegetable name

    @pizza.vegetable = name end #def add_sauce, add_cheese... def create_pizza name @pizza = Pizza.new(name) yield if block_given? end new_pizza 'Frango Catupiry' do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry' add_topping 'chicken' end
  11. Pizza = Struct.new(:name, :vegetable, :cheese, :sauce, :topping) def add_vegetable name

    @pizza.vegetable = name end #def add_sauce, add_cheese... def create_pizza name @pizza = Pizza.new(name) yield if block_given? end new_pizza 'Frango Catupiry' do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry' add_topping 'chicken' end
  12. Pizza = Struct.new(:name, :vegetable, :cheese, :sauce, :topping) def add_vegetable name

    @pizza.vegetable = name end #def add_sauce, add_cheese... def create_pizza name @pizza = Pizza.new(name) yield if block_given? end new_pizza 'Frango Catupiry' do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry' add_topping 'chicken' end
  13. Pizza = Struct.new(:name, :vegetable, :cheese, :sauce, :topping) def add_vegetable name

    @pizza.vegetable = name end #def add_sauce, add_cheese... def create_pizza name @pizza = Pizza.new(name) yield if block_given? end new_pizza 'Frango Catupiry' do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry' add_topping 'chicken' end
  14. Proc/lambda times_two = Proc.new do |n| n * 2 end

    times_two = lambda do |n| n * 2 end times_two.call(2) #=> 4
  15. ampersand square = Proc.new do |n| n ** 2 end

    [1, 2].each(square) #won't work [1, 2].each(&square)#will convert #the proc to a block
  16. ampersand square = Proc.new do |n| n ** 2 end

    [1, 2].each(square) #won't work [1, 2].each(&square)#will convert #the proc to a block
  17. class Alphabet def self.each_letter(&block) #converts block into proc ('A'..'Z').each(&block) #converts

    proc into block end end Alphabet.each_letter { |l| puts l } ampersand
  18. class Alphabet def self.each_letter(&block) #converts block into proc ('A'..'Z').each(&block) #converts

    proc into block end end Alphabet.each_letter { |l| puts l } ampersand
  19. class Alphabet def self.each_letter(&block) #converts block into proc ('A'..'Z').each(&block) #converts

    proc into block end end Alphabet.each_letter { |l| puts l } ampersand
  20. class Alphabet def self.each_letter(&block) #converts block into proc ('A'..'Z').each(&block) #converts

    proc into block end end Alphabet.each_letter { |l| puts l } #=> 'A' #=> 'B' #=> 'C' ampersand
  21. instance_eval class User attr_accessor :name end user = User.new user.name

    #=> nil user.instance_eval do @name = 'My new name' end user.name #=> 'My new name'
  22. instance_eval class User attr_accessor :name end user = User.new user.name

    #=> nil user.instance_eval do @name = 'My new name' end user.name #=> 'My new name'
  23. instance_eval class User attr_accessor :name end user = User.new user.name

    #=> nil user.instance_eval do @name = 'My new name' end user.name #=> 'My new name'
  24. instance_eval class User attr_accessor :name end user = User.new user.name

    #=> nil user.instance_eval do @name = 'My new name' end user.name #=> 'My new name'
  25. instance_eval class User attr_accessor :name end user = User.new user.name

    #=> nil user.instance_eval do @name = 'My new name' end user.name #=> 'My new name'
  26. #pizza.rb class Pizza attr_accessor :name, :vegetable, :cheese, :sauce, :toppings def

    initialize name @name = name end def add_vegetable name @vegetable = name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end def add_toppings *args raise "Too many toppings man!" if args.size > 4 @toppings = args end end
  27. #pizza.rb class Pizza attr_accessor :name, :vegetable, :cheese, :sauce, :toppings def

    initialize name @name = name end def add_vegetable name @vegetable = name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end def add_toppings *args raise "Too many toppings man!" if args.size > 4 @toppings = args end end
  28. #pizza.rb class Pizza #... def add_toppings *args raise "Too many

    toppings man!" if args.size > 4 @toppings = args end end
  29. #cookbook.rb class Cookbook @pizzas = [] def self.new_pizza name, &block

    pizza = Pizza.new(name) pizza.instance_eval(&block) if block_given? @pizzas << pizza end def self.pizzas @pizzas end end
  30. #cookbook.rb class Cookbook @pizzas = [] def self.new_pizza name, &block

    pizza = Pizza.new(name) pizza.instance_eval(&block) if block_given? @pizzas << pizza end def self.pizzas @pizzas end end
  31. #cookbook.rb class Cookbook @pizzas = [] def self.new_pizza name, &block

    pizza = Pizza.new(name) pizza.instance_eval(&block) if block_given? @pizzas << pizza end def self.pizzas @pizzas end end
  32. #cookbook.rb class Cookbook @pizzas = [] def self.new_pizza name, &block

    pizza = Pizza.new(name) pizza.instance_eval(&block) if block_given? @pizzas << pizza end def self.pizzas @pizzas end end
  33. #pizza.rb class Pizza # ... def add_vegetable name @vegetable =

    name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end # ... end
  34. #pizza.rb class Pizza # ... def add_vegetable name @vegetable =

    name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end # ... end
  35. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  36. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  37. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  38. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  39. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  40. class User attr_accessor :name def forget_my_name! self.name = nil end

    end user = User.new user.name = 'Joao' user.name #=> 'Joao' user.forget_my_name! user.name #=> nil
  41. user = User.new user.name = 'Joao' user.name #=> "Joao" user.send

    'name' #=> "Joao" user.send 'name=', 'Pedro' user.name #=> Pedro
  42. user = User.new user.name = 'Joao' user.name #=> "Joao" user.send

    'name' #=> "Joao" user.send 'name=', 'Pedro' user.name #=> Pedro
  43. user = User.new user.name = 'Joao' user.name #=> "Joao" user.send

    'name' #=> "Joao" user.send 'name=', 'Pedro' user.name #=> Pedro
  44. user = User.new user.name = 'Joao' user.name #=> "Joao" user.send

    'name' #=> "Joao" user.send 'name=', 'Pedro' user.name #=> Pedro
  45. class Comment def method_missing(method_name, *args) puts "Voce tentou chamar o

    metodo #{method_name} com os argumentos #{args}" end end comment = Comment.new comment.nao_existe_blabla 1, 3, '5' #=>Voce tentou chamar o metodo nao_existe_blabla com os argumentos [1,3,'5']
  46. class Comment def method_missing(method_name, *args) puts "Voce tentou chamar o

    metodo #{method_name} com os argumentos #{args}" end end comment = Comment.new comment.nao_existe_blabla 1, 3, '5' #=>Voce tentou chamar o metodo nao_existe_blabla com os argumentos [1,3,'5']
  47. class Comment def method_missing(method_name, *args) puts "Voce tentou chamar o

    metodo #{method_name} com os argumentos #{args}" end end comment = Comment.new comment.nao_existe_blabla 1, 3, '5' #=>Voce tentou chamar o metodo nao_existe_blabla com os argumentos [1,3,'5']
  48. #pizza.rb class Pizza # ... def add_vegetable name @vegetable =

    name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end # ... end
  49. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  50. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  51. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  52. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  53. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  54. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  55. class Pizza #... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) #pizza.cheese= 'cheddar' else super end end end
  56. #pizza.rb class Pizza # ... def add_vegetable name @vegetable =

    name end def add_sauce name @sauce = name end def add_cheese name @cheese = name end # ... end
  57. class Pizza ... private def method_missing method_sym, *args method_name =

    method_sym.to_s if method_name.start_with? "add_" attribute_name = method_name.split(/^add_/)[1] self.send(attribute_name + "=", args[0]) else super end end end
  58. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end
  59. #bake_time.rb class BakeTime attr_accessor :duration, :temperature def initialize duration @duration

    = duration end def on temperature @temperature = temperature end end
  60. #bake_time.rb class BakeTime attr_accessor :duration, :temperature def initialize duration @duration

    = duration end def on temperature @temperature = temperature end end
  61. #bake_time.rb class BakeTime attr_accessor :duration, :temperature def initialize duration @duration

    = duration end def on temperature @temperature = temperature end end
  62. #bake_time.rb class BakeTime attr_accessor :duration, :temperature def initialize duration @duration

    = duration end def on temperature @temperature = temperature end end
  63. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end
  64. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end
  65. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end
  66. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end
  67. Cookbook.new_pizza "Frango Catupiry" do add_vegetable 'tomatoes' add_sauce 'barbecue' add_cheese 'catupiry'

    add_toppings 'chicken', 'bacon' bake_for(15.minutes).on(:high_temperature) end