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

Riding Rails for 10 Years

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Riding Rails for 10 Years

This talk goes through 10 years of Rails by looking at Shopify and how the project, and Rails, has evolved over that time.

Avatar for John Duff

John Duff

April 21, 2015

More Decks by John Duff

Other Decks in Technology

Transcript

  1. 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 => nil end
  2. 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]) end end
  3. 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) end end
  4. 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 end end
  5. -------------------------------------------------- Language files code -------------------------------------------------- Ruby 177 4967 Javascript 10

    2436 YAML 33 1465 Ruby HTML 61 1399 CSS 4 638 SQL 2 573 HTML 2 41 Bourne Again Shell 1 2 -------------------------------------------------- SUM: 290 11521 --------------------------------------------------
  6. 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 end end
  7. 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 end end
  8. Rails&2.0:&rescue_from # before def rescue_action(e) case e when MerchantCredentialError when

    IrreparableGoogleCheckoutError when ActiveRecord::RecordNotFound else end end # after rescue_from MerchantCredentialError do |exception| response.headers["WWW-Authenticate"] = %(Basic realm="Ping Backend") render :status => "401 Unauthorized" end
  9. Rails&2.0:&Fixture&dependencies # before bigcheese_blog: id: 1 shop_id: 1 title: Mah

    Blog updated_at: 2006-02-02 # after bigcheese_blog: shop: snowdevil title: Mah Blog updated_at: 2006-02-02 # Fixtures.identify(:snowdevil) when ambiguous
  10. Rails&2.0:&Rou-ng&namespaces # before map.resources :fulfillment_services, :path_prefix => 'admin/preferences', :controller =>

    'admin/preferences/fulfillment_services' # after admin.namespace :admin do |admin| admin.namespace :preferences do |prefs| prefs.resources :fulfillment_services end end
  11. Rails&2.3:&Rack # config/environment.rb ActionController::Dispatcher.middleware.insert_before 'Rack::Lock', CommonBlacklist # lib/common_blacklist.rb class 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 end end
  12. Rails&2.3:&accepts_nested_a2ributes # before class 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 shenanigans end # after class ApiClient < ActiveRecord::Base attr_accessible :links_attributes accepts_nested_attributes_for :links, :allow_destroy => true end
  13. Rails&3:&Arel # before class StoredAsset < ActiveRecord::Base def self.with_prefix(prefix) scoped(:conditions

    => {:prefix => prefix.to_s}) end end # after class StoredAsset < ActiveRecord::Base scope :with_prefix, lambda { |prefix| where(:prefix => prefix.to_s) } end
  14. Rails&3:&Bundler # before # config/environment.rb Rails::Initializer.run do |config| config.gem "right_aws"

    end # after # Gemfile source "http://gems.rubyforge.org" source "http://gems.github.com" gem "rack", '1.0.1' gem "rails", '2.3.5'
  15. 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 end end # views/admin/customers/show.html+mobile.erb