Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Riding rails for 10 years

963 views

Published on

Published in: Software
  • Be the first to comment

  • Be the first to like this

Riding rails for 10 years

  1. 1. Riding&Rails&for&10&Years John%Duff
  2. 2. Overview • Why%look%at%Shopify? • History%of%Rails%updates • Hardest%things? • Why%upgrade? • Recommenda;ons
  3. 3. 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
  4. 4. ~45!different!versions!of!Rails!in!produc3on
  5. 5. Going&back&in&+me
  6. 6. Going&back&in&+me • MVC%pa(erns%already%established • Tes5ng%already%baked%in • deploy.rb
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. 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
  11. 11. Going&back&in&+me:&more • Sweepers • Observers • rhtml/views • Dependencies/in/vendor/or/submodules • FastCGI/to/interface/with/the/webserver
  12. 12. -------------------------------------------------- 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 --------------------------------------------------
  13. 13. A"lot"has"changed,"but"a"lot"is"s2ll"the"same
  14. 14. Rails&1.2
  15. 15. Rails&1.2 • REST&and&Resources • Mul2byte&support • Rou2ng&and&auto8loading&rewri;en • Formats&and&respond_to
  16. 16. 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
  17. 17. 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
  18. 18. Rails&1.2:&more • Ini%al(RESTful(urls(looked(like(/orders/1;edit • Changed(in(Rails(1.2.4(to(/orders/1/edit
  19. 19. Rails&2.0
  20. 20. Rails&2.0 • Added%rescue_from • Fixture%dependencies • Rou5ng%namespaces • Mul5%view%responses • Ac5onWebService%out,%Ac5veResource%in • JSON%serializa5on
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. Rails&2.1
  25. 25. Rails&2.1:&config.gems # config/environment.rb Rails::Initializer.run do |config| config.gem "right_aws" config.gem "entp-multipass", :source => 'http://gems.github.com' end
  26. 26. Rails&2.3
  27. 27. Rails&2.3 • Rack • accepts_nested_a-ributes • find_in_batches • improvements7to7Rails7Engines • Rails7metal • Applica<on7templates
  28. 28. 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
  29. 29. 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
  30. 30. Rails&3
  31. 31. Rails&3 • Arel • Bundler • Ac+ve-model-broken-up • js/test/orm-agnos+c
  32. 32. 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
  33. 33. 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'
  34. 34. Rails&3.1&and&3.2
  35. 35. 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
  36. 36. Rails&4.0
  37. 37. Rails&4.0 • Ruby&2 • Turbolinks • Russian&doll&caching • Strong&parameters • Remove&observers • Remove&hash&and&dynamic&finders
  38. 38. Rails&4.0:&dynamic&finders # before shop.customers.find_or_initialize_by_email(data[:email]) PaymentProvider.find_all_by_type(:direct) # after shop.customers.where(data.slice(:email)).first_or_initialize PaymentProvider.where(type: 'direct')
  39. 39. Rails&4.1
  40. 40. Rails&4.1 • Spring(applica,on(preloader • Variant(templates
  41. 41. 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
  42. 42. That%brings%us%to%today
  43. 43. Started'easy,'got'hard
  44. 44. Hardest(things? • Marshaling+changes • Maintaining+momentum+with+a+large+team • Large+codebase+with+lots+of+edge+cases • Performance+regressions
  45. 45. Why$upgrade? • New%features • Be-er%security • Hiring • Codebase%longevity
  46. 46. 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
  47. 47. john.duff@shopify.com @johnduff

×