ruby over rails
DESCRIPTION
Ruby has a powerful set of libs and tools that we don't use in our day to day jobs as ruby developers. The main reason for this is because we force logic where it should not be: the template. By refactoring a simple application I'll show you how to unlock the power of Ruby and how to write code that you will love to work with in the future.TRANSCRIPT
Ruby over RailsRuby Day 2014
Giuseppe Modarelli
Ruby DeveloperJavascript Developer
Drummer
gmodarelli gmodarelli gmodarelli.com
xml2json
Why Ruby over Rails?
Your app uses Rails
Your app is not Rails
What’s Rails?
Corey Haines
A set of helpers that takes us from a url to a method for both incoming requests and outgoing responses (HTTP)
An ORM (DoneRecord) that serves as a database adapter
Corey Haines
HTTP DBYOUR APP
A real life example
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
WHAT THE SHISH!!?!?!?
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id= <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> </div>
How can we solve this?
<div id= <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> </div>
Don’t start over!
<div id= <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> </div>
Clean up existing code
<div id="dates"> <div class="date <%= @contract.signed_at.past? ? 'done' : 'pending' %>"> <% month = @contract.signed_at.strftime('%B').downcase %> <% day = @contract.signed_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.activation_date.past? ? 'done' : 'pending' %>"> <% month = @contract.activation_date.strftime('%B').downcase %> <% day = @contract.activation_date.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> ! <div class="date <%= @contract.invoices.first.sent_at.past? ? 'done' : 'pending' %>"> <% month = @contract.invoices.first.sent_at.strftime(‘%B’).downcase %> <% day = @contract.invoices.first.sent_at.day.to_s %> <div class="<%= month %>"></div> <div class="<%= day %>"></div> </div> </div>
<div id= <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> ! <div <% month = <% day = <div <div </div> </div>
HAML
%div{ id: "dates" } %div{ class: "date #{@contract.signed_at.past? ? 'done' : 'pending'}" } - month = @contract.signed_at.strftime('%B').downcase - day = @contract.signed_at.day.to_s %div{ class: "#{month}" } %div{ class: "#{day}" } %div{ class: "date #{@contract.activation_date.past? ? 'done' : 'pending'}" } - month = @contract.activation_date.strftime('%B').downcase - day = @contract.activation_date.day.to_s %div{ class: "#{month}" } %div{ class: "#{day}" } %div{ class: "date #{@contract.first_invoice_sent_at.past? ? 'done' : 'pending'}" } - month = @contract.first_invoice_sent_at.strftime('%B').downcase - day = @contract.first_invoice_sent_at.day.to_s %div{ class: "#{month}" } %div{ class: "#{day}" }
%div{ id: "dates" } %div{ class: "date #{@contract.signed_at.past? ? 'done' : 'pending'} - month = @contract.signed_at.strftime('%B') - day = %div{ class: "#{month} %div %div{ class: "date #{@contract.activation_date.past? ? 'done' : 'pending'} - month = @contract.activation_date.strftime('%B') - day = %div{ class: "#{month} %div %div{ class: "date #{@contract.first_invoice_sent_at.past? ? 'done' : 'pending'} - month = @contract.first_invoice_sent_at.strftime('%B') - day = %div{ class: "#{month} %div
SLIM
div#dates div class="date #{@contract.signed_at.past? ? 'done' : 'pending'}" - month = @contract.signed_at.strftime('%B').downcase - day = @contract.signed_at.day.to_s div class=month div class=day div class="date #{@contract.activation_date.past? ? 'done' : 'pending'}" - month = @contract.activation_date.strftime('%B').downcase - day = @contract.activation_date.day.to_s div class=month div class=day div class="date #{@contract.first_invoice_sent_at.past? ? 'done' : 'pending'}" - month = @contract.first_invoice_sent_at.strftime('%B').downcase - day = @contract.first_invoice_sent_at.day.to_s div class=month div class=day
div#dates div - month = - day = div div div - month = - day = div div div - month = - day = div div
SLIM LOGIC-LESS
div#dates div - month = - day = div div div - month = - day = div div div - month = - day = div div
Programming by Wishful Thinking
div#dates - @dates div class="date #{status}" div class=month div class=day
div - div div div
Ok cool. But we do need that logic!
div - div div div
M V C
div - div div div
M V C
div - div div div
M V C
div - div div div
M V C
div - div div div
PORO
div#dates - @dates div class="date #{status}" div class=month div class=day
describe Presenters::CalendarWidget do let(:date) { DateTime.parse '2014/09/26 08:00:00' } ! describe '#status' do it 'is "done" when the date is in the past' do calendar_widget = described_class.new date expect(calendar_widget.status).to eq('done') end end end
class Presenters::CalendarWidget def initialize date end ! def status "done" end end
describe Presenters::CalendarWidget do let(:date) { DateTime.parse '2014/09/26 08:00:00' } ! describe '#status' do it 'is "done" when the date is in the past' do calendar_widget = described_class.new date expect(calendar_widget.status).to eq('done') end ! it 'is "pending" when the date is in the future' do calendar_widget = described_class.new date + 1.day expect(calendar_widget.status).to eq('pending') end end end
class Presenters::CalendarWidget def initialize date @date = date end ! def status return "done" if @date.past? "pending" end end
describe Presenters::CalendarWidget do let(:date) { DateTime.parse '2014/09/26 08:00:00' } ! . . . ! describe '#month' do it 'is the month downcased' do calendar_widget = described_class.new date expect(calendar_widget.month).to eq('september') end end end
class Presenters::CalendarWidget . . . ! def month @date.strftime('%B').downcase end end
describe Presenters::CalendarWidget do let(:date) { DateTime.parse '2014/09/26 08:00:00' } ! . . . ! describe '#day' do it 'is the day of the month' do calendar_widget = described_class.new date expect(calendar_widget.day).to eq('26') end end end
class Presenters::CalendarWidget ! . . . ! def day @date.day.to_s end end
class Presenters::CalendarWidget def initialize date @date = date end ! def status return "done" if @date.past? "pending" end ! def month @date.strftime('%B').downcase end ! def day @date.day.to_s end end
div#dates - @dates div class="date #{status}" div class=month div class=day
Where does @dates come from?
class DashboardController def index @dates = [ Presenter::CalendarWidget.new(current_user.contract.signed_at), Presenter::CalendarWidget.new(current_user.contract.activation_date), Presenter::CalendarWidget.new(current_user.contract.invoices.first.sent_at) ] end end
class DashboardController def index @dates = [ Presenter::CalendarWidget.new(current_user.contract.signed_at), Presenter::CalendarWidget.new(current_user.contract.activation_date), Presenter::CalendarWidget.new(current_user.contract.invoices.first.sent_at) ] end end
class DashboardController def index @dates = Presenters::CalendarWidget.for([ current_user.contract.signed_at, current_user.contract.activation_date, current_user.contract.invoices.first.sent_at ]) end end
class DashboardController def index @dates = Presenters::CalendarWidget.for( current_user.relevant_contract_dates ) end end
describe Presenters::CalendarWidget do let(:date) { DateTime.parse '2014/09/26 08:00:00' } ! . . . ! describe '.for' do it 'returns a list of widgets' do dates = [ date, date + 2.month ] widgets = described_class.for dates ! expect(widgets.count).to be(2) expect(widgets.first.status).to be('done') expect(widgets.last.status).to be('pending') end end end
class Presenters::CalendarWidget . . . ! def self.for dates dates.each.map do |date| new date end end end
class User < ActiveRecord::Base . . . ! def relevant_contract_dates [ contract.signed_at, contract.activation_date, contract.invoices.first.sent_at ] end end
class User < ActiveRecord::Base . . . ! delegate: :signed_at, :activation_date, :invoices, to: :contract ! def relevant_contract_dates [ signed_at, activation_date, invoices.first.sent_at ] end end
class User < ActiveRecord::Base . . . ! delegate: :signed_at, :activation_date, :first_invoice_date, to: :contract ! def relevant_contract_dates [ signed_at, activation_date, first_invoice_date ] end end
class Contract < ActiveRecord::Base . . . ! def first_invoice_date invoices.first.sent_at end end
class Contract < ActiveRecord::Base . . . ! def first_invoice_date first_invoice.sent_at end ! def first_invoice invoices.first end end
class Contract < ActiveRecord::Base . . . ! def first_invoice_date first_invoice.sent_at end ! def first_invoice invoices.first || invoices.new( sent_at: activation_date + 2.month) end end
class Presenters::CalendarWidget def self.for dates dates.each.map {|date| new date } end ! def initialize date @date = date end ! def status return "done" if @date.past? "pending" end ! def month @date.strftime('%B').downcase end ! def day @date.day.to_s end end
class Collection::CalendarWidgets def self.for dates new dates end ! def all @dates end ! private def initialize dates @dates = dates.map do |date| Presenter::CalendarWidget.new date end end end
class Collection::CalendarWidgets . . . ! def pending all.select { |date| date.status == "pending" } end ! def done all.select { |date| date.status == "done" } end end
When to use these techniques
DHH Style
BULLSHIT!!!
ALWAYS
Thank You
Giuseppe Modarelli
Ruby DeveloperJavascript Developer
Drummer
gmodarelli gmodarelli gmodarelli.com