Ruby DSL
Crafting beautiful code
● You know basic Ruby
● OR get you interested to know Ruby
● Feel free to ask questions
● Will only scratch the surface
● After this, you can write a basic Ruby DSL
Expectations
DSL !!!
describe '#destroy'
context 'when resource is found'
it 'has 200 status code if logged in'
expect response to respond with 200
end
end
end
DSL ???
Test
X
DSL you might have seen in Ruby
describe '#destroy' do
context 'when resource is found' do
it 'has 200 status code if logged in' do
expect(response).to respond_with 200
end
end
end
RSpec
DSL you might have seen in Ruby
create_table :users do |t|
t.string :name
t.attachment :avatar
t.timestamps
end
DB Migrations
DSL you might have seen in Ruby
Rails.application.routes.draw do
root 'pages#home'
resources :pages, only: [:index, :show]
end
Rails Routing
What is DSL??
API that is tailored to
express Domain Logic
Language
VS
DSL
Why DSL ???
● Simple to express
● Embraces domain in code
● Powerful abstraction that allows you to
change
Let’s start
Keys to Ruby DSL
Code blocks
instance_eval
Code blocks
Ruby code block
def report
puts "Header"
yield
puts "Footer"
end
report do
puts "From block"
end
Code Output
Header
From block
Footer
REPL
instance_eval
class Report
def initialize(&block)
puts "Header"
instance_eval &block
puts "Footer"
end
end
Report.new do
puts "From block"
end
Ruby instance_eval
class Report
def initialize(&block)
puts "Header"
instance_eval &block
puts "Footer"
end
def my_print(str)
puts str
end
end
Ruby instance_eval - 02
Report.new do
my_print "From block"
end
REPL
class Report
def initialize(data, &block)
@data = data; @columns = []
instance_eval &block
end
def column(column_name) @columns << column_name end
def print
@data.each { |row| @columns.each { |column| puts row[column] } }
end
end
Ruby instance_eval - 03
data = [
{name: 'Jitu', age: 34},
{name: 'Razeen', age: 3}
]
report = Report.new(data) do
column :name
end
report.print()
Ruby instance_eval - 03
Jitu
Razeen
REPL
My experience
In one of the rails project I worked on had a tons of reports, which needed the
following features
● Queries, which are easy to understand and change
● Filters
● Pagination
● Generate PDF, CSV, and email those reports
● Generate graph in HTML and in PDF
● Had complex rowspan and colspan
DSL for generating reports
def index
reporter(Invoice.scoped) do
filter :title, type: :text
filter :created_at, type: :date
column :title { |invoice| link_to invoice.title, invoice }
column :total_paid, show_total: true
column :total_charged, show_total: true
column :paid
end
end
Demo
Author
A.K.M. Ashrafuzzaman
Software Engineer,
Newscred.
http://ashrafuzzaman.github.io
References
Link to this slide
Blogs
● DSL QandA by Martin Fowler
● Creating a Ruby DSL, by Leigh Halliday
Source code
● Source codes for this slide
● query_report gem

Ruby DSL

  • 1.
  • 2.
    ● You knowbasic Ruby ● OR get you interested to know Ruby ● Feel free to ask questions ● Will only scratch the surface ● After this, you can write a basic Ruby DSL Expectations
  • 3.
  • 4.
    describe '#destroy' context 'whenresource is found' it 'has 200 status code if logged in' expect response to respond with 200 end end end DSL ??? Test X
  • 5.
    DSL you mighthave seen in Ruby describe '#destroy' do context 'when resource is found' do it 'has 200 status code if logged in' do expect(response).to respond_with 200 end end end RSpec
  • 6.
    DSL you mighthave seen in Ruby create_table :users do |t| t.string :name t.attachment :avatar t.timestamps end DB Migrations
  • 7.
    DSL you mighthave seen in Ruby Rails.application.routes.draw do root 'pages#home' resources :pages, only: [:index, :show] end Rails Routing
  • 8.
    What is DSL?? APIthat is tailored to express Domain Logic
  • 9.
  • 10.
    Why DSL ??? ●Simple to express ● Embraces domain in code ● Powerful abstraction that allows you to change
  • 11.
  • 12.
    Keys to RubyDSL Code blocks instance_eval
  • 13.
  • 14.
    Ruby code block defreport puts "Header" yield puts "Footer" end report do puts "From block" end Code Output Header From block Footer REPL
  • 15.
  • 16.
    class Report def initialize(&block) puts"Header" instance_eval &block puts "Footer" end end Report.new do puts "From block" end Ruby instance_eval
  • 17.
    class Report def initialize(&block) puts"Header" instance_eval &block puts "Footer" end def my_print(str) puts str end end Ruby instance_eval - 02 Report.new do my_print "From block" end REPL
  • 18.
    class Report def initialize(data,&block) @data = data; @columns = [] instance_eval &block end def column(column_name) @columns << column_name end def print @data.each { |row| @columns.each { |column| puts row[column] } } end end Ruby instance_eval - 03
  • 19.
    data = [ {name:'Jitu', age: 34}, {name: 'Razeen', age: 3} ] report = Report.new(data) do column :name end report.print() Ruby instance_eval - 03 Jitu Razeen REPL
  • 20.
    My experience In oneof the rails project I worked on had a tons of reports, which needed the following features ● Queries, which are easy to understand and change ● Filters ● Pagination ● Generate PDF, CSV, and email those reports ● Generate graph in HTML and in PDF ● Had complex rowspan and colspan
  • 21.
    DSL for generatingreports def index reporter(Invoice.scoped) do filter :title, type: :text filter :created_at, type: :date column :title { |invoice| link_to invoice.title, invoice } column :total_paid, show_total: true column :total_charged, show_total: true column :paid end end
  • 22.
  • 23.
  • 24.
    References Link to thisslide Blogs ● DSL QandA by Martin Fowler ● Creating a Ruby DSL, by Leigh Halliday Source code ● Source codes for this slide ● query_report gem