Top Banner
Riding Rails for 10 Years John Duff
50

Riding rails for 10 years

Jul 17, 2015

Download

Software

jduff
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Riding rails for 10 years

Riding&Rails&for&10&YearsJohn%Duff

Page 2: Riding rails for 10 years

Overview• Why%look%at%Shopify?

• History%of%Rails%updates

• Hardest%things?

• Why%upgrade?

• Recommenda;ons

Page 3: Riding rails for 10 years

Why$look$at$Shopify?

• Started(around(the(same(/me(as(Rails

• Never(rewri6en

• Has(used(Rails(pre(1.0(to(4.2

• Git(repository(holds(most(of(this(history

Page 4: Riding rails for 10 years
Page 5: Riding rails for 10 years
Page 6: Riding rails for 10 years

~45!different!versions!of!Rails!in!produc3on

Page 7: Riding rails for 10 years

Going&back&in&+me

Page 8: Riding rails for 10 years
Page 9: Riding rails for 10 years

Going&back&in&+me

• MVC%pa(erns%already%established

• Tes5ng%already%baked%in

• deploy.rb

Page 10: Riding rails for 10 years

Going&back&in&+me:&rou+ng

ActionController::Routing::Routes.draw do |map| map.index '', :controller => 'shop'

# namespaces map.connect 'admin', :controller => 'admin/auth', :action => 'login' map.checkout 'checkout', :controller => 'checkout/standard', :action => 'index'

# media files map.connect 'media/:action/:id/:filename', :controller => 'media', :action => 'image'

# admin map.connect ':controller/:action/:id', :action => 'index', :id => nilend

Page 11: Riding rails for 10 years

Going&back&in&+me:&controller

class Admin::OrdersController < AdminAreaController def index list render :action => 'list' end

def list @pages = Paginator.new(self, shop.orders.count, 20, @params[:page]) @orders = shop.orders.find(:all, :limit => 20, :offset => @pages.current.offset) end

def show @order = shop.orders.find(@params[:id], :include => [:line_items, :payments]) endend

Page 12: Riding rails for 10 years

Going&back&in&+me:&model

class Order < ActiveRecord::Base belongs_to :shop has_many :line_items

belongs_to :billing_address, :class_name => 'Address'

validates_presence_of :email, :shop validates_format_of :email, :with => Format::EMAIL, :message => 'not a valid email'

serialize :receipt, Hash attr_accessible :email

def deliver_confirmation_email CheckoutMailer.deliver_user_confirmation(self) endend

Page 13: Riding rails for 10 years

Going&back&in&+me:&javascript

module AjaxHelper def appear(id, where = nil) case where when :first "new Effect.Appear($('#{id}').firstChild)" else "new Effect.Appear('#{id}')" end endend

Page 14: Riding rails for 10 years

Going&back&in&+me:&more

• Sweepers

• Observers

• rhtml/views

• Dependencies/in/vendor/or/submodules

• FastCGI/to/interface/with/the/webserver

Page 15: Riding rails for 10 years

--------------------------------------------------Language files code--------------------------------------------------Ruby 177 4967Javascript 10 2436YAML 33 1465Ruby HTML 61 1399CSS 4 638SQL 2 573HTML 2 41Bourne Again Shell 1 2--------------------------------------------------SUM: 290 11521--------------------------------------------------

Page 16: Riding rails for 10 years

A"lot"has"changed,"but"a"lot"is"s2ll"the"same

Page 17: Riding rails for 10 years

Rails&1.2

Page 18: Riding rails for 10 years

Rails&1.2

• REST&and&Resources

• Mul2byte&support

• Rou2ng&and&auto8loading&rewri;en

• Formats&and&respond_to

Page 19: Riding rails for 10 years

Rails&1.2:&Formats&and&respond_to

ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action.:format' map.connect ':controller/:action/:id.:format'end

class Admin::OrdersController < AdminAreaController def list @pages = Paginator.new(self, shop.orders.count(:all), 25, params[:page]) @orders = shop.orders.find(:all, :limit => 25, :offset => @pages.current.offset)

respond_to do |format| format.html { render :action => 'list' } format.csv { render_export_file('orders.csv', Mime::CSV, CSV.export(@orders)) } end endend

Page 20: Riding rails for 10 years

Rails&1.2:&REST&and&Resources

ActionController::Routing::Routes.draw do |map| map.resources :collects, :path_prefix => "admin", :controller => "admin/collects"end

class Admin::CollectsController < AdminAreaController def create @collect = Collect.new(:product => @product, :collection => @collection) @collect.save ? head(:created) : head(:precondition_failed) end

def destroy @collect = Collect.find(params[:id])

if @collect and shop.products.exists?(@collect.product_id) @collect.destroy head :ok else head :not_found end endend

Page 21: Riding rails for 10 years

Rails&1.2:&more

• Ini%al(RESTful(urls(looked(like(/orders/1;edit

• Changed(in(Rails(1.2.4(to(/orders/1/edit

Page 22: Riding rails for 10 years

Rails&2.0

Page 23: Riding rails for 10 years

Rails&2.0

• Added%rescue_from

• Fixture%dependencies

• Rou5ng%namespaces

• Mul5%view%responses

• Ac5onWebService%out,%Ac5veResource%in

• JSON%serializa5on

Page 24: Riding rails for 10 years

Rails&2.0:&rescue_from

# beforedef rescue_action(e) case e when MerchantCredentialError when IrreparableGoogleCheckoutError when ActiveRecord::RecordNotFound else endend

# afterrescue_from MerchantCredentialError do |exception| response.headers["WWW-Authenticate"] = %(Basic realm="Ping Backend") render :status => "401 Unauthorized"end

Page 25: Riding rails for 10 years

Rails&2.0:&Fixture&dependencies

# beforebigcheese_blog: id: 1 shop_id: 1 title: Mah Blog updated_at: 2006-02-02

# afterbigcheese_blog: shop: snowdevil title: Mah Blog updated_at: 2006-02-02

# Fixtures.identify(:snowdevil) when ambiguous

Page 26: Riding rails for 10 years

Rails&2.0:&Rou-ng&namespaces

# beforemap.resources :fulfillment_services, :path_prefix => 'admin/preferences', :controller => 'admin/preferences/fulfillment_services'

# afteradmin.namespace :admin do |admin| admin.namespace :preferences do |prefs| prefs.resources :fulfillment_services endend

Page 27: Riding rails for 10 years

Rails&2.1

Page 28: Riding rails for 10 years

Rails&2.1:&config.gems

# config/environment.rbRails::Initializer.run do |config| config.gem "right_aws" config.gem "entp-multipass", :source => 'http://gems.github.com'end

Page 29: Riding rails for 10 years

Rails&2.3

Page 30: Riding rails for 10 years

Rails&2.3

• Rack

• accepts_nested_a-ributes

• find_in_batches

• improvements7to7Rails7Engines

• Rails7metal

• Applica<on7templates

Page 31: Riding rails for 10 years

Rails&2.3:&Rack

# config/environment.rbActionController::Dispatcher.middleware.insert_before 'Rack::Lock', CommonBlacklist

# lib/common_blacklist.rbclass CommonBlacklist def initialize(app) @app = app end

def call(env) if env['REQUEST_URI'] =~ /^\/feedsplitter.php/ [404, {"Content-Type" => "text/plain"}, ['[Filtered]']] else @app.call(env) end endend

Page 32: Riding rails for 10 years

Rails&2.3:&accepts_nested_a2ributes

# beforeclass ApiClient < ActiveRecord::Base attr_accessible :new_link_attributes, :existing_link_attributes

def new_link_attributes=(link_attributes) link_attributes.each do |attributes| links.build(attributes) end end # more shenanigansend

# afterclass ApiClient < ActiveRecord::Base attr_accessible :links_attributes

accepts_nested_attributes_for :links, :allow_destroy => trueend

Page 33: Riding rails for 10 years

Rails&3

Page 34: Riding rails for 10 years

Rails&3

• Arel

• Bundler

• Ac+ve-model-broken-up

• js/test/orm-agnos+c

Page 35: Riding rails for 10 years

Rails&3:&Arel

# beforeclass StoredAsset < ActiveRecord::Base def self.with_prefix(prefix) scoped(:conditions => {:prefix => prefix.to_s}) endend

# afterclass StoredAsset < ActiveRecord::Base scope :with_prefix, lambda { |prefix| where(:prefix => prefix.to_s) }end

Page 36: Riding rails for 10 years

Rails&3:&Bundler

# before# config/environment.rbRails::Initializer.run do |config| config.gem "right_aws"end

# after# Gemfilesource "http://gems.rubyforge.org"source "http://gems.github.com"gem "rack", '1.0.1'gem "rails", '2.3.5'

Page 37: Riding rails for 10 years

Rails&3.1&and&3.2

Page 38: Riding rails for 10 years

Rails&3.1&and&3.2

• Assets&pipeline

• JQuery&the&default&JS&library

• Lots&of&internal&API&changes

• 248&changed&files&with&1,366&addiAons&and&1,656&deleAons

Page 39: Riding rails for 10 years

Rails&4.0

Page 40: Riding rails for 10 years

Rails&4.0

• Ruby&2

• Turbolinks

• Russian&doll&caching

• Strong&parameters

• Remove&observers

• Remove&hash&and&dynamic&finders

Page 41: Riding rails for 10 years

Rails&4.0:&dynamic&finders

# beforeshop.customers.find_or_initialize_by_email(data[:email])PaymentProvider.find_all_by_type(:direct)

# aftershop.customers.where(data.slice(:email)).first_or_initializePaymentProvider.where(type: 'direct')

Page 42: Riding rails for 10 years

Rails&4.1

Page 43: Riding rails for 10 years

Rails&4.1

• Spring(applica,on(preloader

• Variant(templates

Page 44: Riding rails for 10 years

Rails&4.1:&Variant&templates

class ApplicationContoller < ActionController::Base before_action :set_mobile_variant

def set_mobile_variant request.variant = :mobile if request.user_agent =~ USER_AGENT_PATTERN endend

# views/admin/customers/show.html+mobile.erb

Page 45: Riding rails for 10 years

That%brings%us%to%today

Page 46: Riding rails for 10 years

Started'easy,'got'hard

Page 47: Riding rails for 10 years

Hardest(things?• Marshaling+changes

• Maintaining+momentum+with+a+large+team

• Large+codebase+with+lots+of+edge+cases

• Performance+regressions

Page 48: Riding rails for 10 years

Why$upgrade?• New%features

• Be-er%security

• Hiring

• Codebase%longevity

Page 49: Riding rails for 10 years

Recommenda)ons• Avoid'monkey'patching'Rails

• Keep'dependencies'low

• Ship'small'changes'early'and'o:en

• Parallel'CI

• Dedicate'a'team

• Ship'to'isolated'produc@on'servers

Page 50: Riding rails for 10 years

[email protected]

@johnduff