Upgrading a big application to Rails 5

Upgrading a big application to Rails 5

In this talk we would take a look in different strategies to upgrade Rails application to the newest version taking as example a huge monolithic Rails application. We will learn what were the biggest challenges and how they could be avoided. We will also learn why the changes were made in Rails and how they work.

0525b332aafb83307b32d9747a93de03?s=128

Rafael França

April 26, 2017
Tweet

Transcript

  1. 2.

    5

  2. 5.
  3. 6.
  4. 8.

    5

  5. 11.
  6. 13.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  7. 14.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  8. 15.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  9. 16.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  10. 17.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  11. 18.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  12. 19.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  13. 20.

    2004 2005 2006 2007 2008 2009 2010 2011 2012 2013

    2014 2015 2016 2017 1.0 2.0 2.1 2.3 3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 2.2 Initial commit Beginning of history 1.1 1.2 2.0 2.1 2.2 2.3 3.0 3.2 4.0 4.1 4.2 5.0 Rails Shopify
  14. 22.
  15. 23.
  16. 24.
  17. 25.
  18. 26.
  19. 27.
  20. 30.

    Long-running branch strategy 1. Create a branch 2. Make all

    changes necessary 3. Merge it 4. ?????? 5. Profit!
  21. 31.
  22. 35.

    if ENV['RAILS_NEXT'] # monkey patching to support dual booting module

    Bundler::SharedHelpers def default_lockfile=(path) @default_lockfile = path end def default_lockfile @default_lockfile ||= Pathname.new("#{default_gemfile}.lock") end end Bundler::SharedHelpers.default_lockfile = Pathname.new("#{Bundler::SharedHelpers.default_gemfile}_next.lock") # Bundler::Dsl.evaluate already called with an incorrect lockfile ... fix it class Bundler::Dsl # A bit messy, this can be called multiple times by bundler, avoid blowing the stack unless self.method_defined? :to_definition_unpatched alias_method :to_definition_unpatched, :to_definition end def to_definition(bad_lockfile, unlock) to_definition_unpatched(Bundler::SharedHelpers.default_lockfile, unlock) end end end if ENV['RAILS_NEXT'] gem 'rails', github: 'rails/rails', branch: '5-0-stable' else gem 'rails', '~> 4.2.7' end
  23. 41.

    Gemfile.shared gem 'sqlite3' gem 'puma', '~> 3.7' gem 'sass-rails', '~>

    5.0' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.2' gem 'turbolinks', '~> 5' gem 'jbuilder', '~> 2.5'
  24. 42.

    config/boot.rb if ENV['RAILS_NEXT'] ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile.next', __dir__) else ENV['BUNDLE_GEMFILE'] ||=

    File.expand_path('../Gemfile', __dir__) end require 'bundler/setup' # Set up gems listed in the Gemfile.
  25. 44.
  26. 48.

    require 'active_support/core_ext/hash/conversions' require 'action_dispatch/http/request' require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch class XmlParamsParser

    def initialize(app) @app = app end def call(env) if params = parse_formatted_parameters(env) env["action_dispatch.request.request_parameters"] = params end @app.call(env) end private def parse_formatted_parameters(env) request = Request.new(env) return false if request.content_length.zero? mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_mime_type if mime_type == Mime[:xml] # Rails 5 removed #deep_munge and replaced it with #normalize_encode_params munger = defined?(Request::Utils) ? Request::Utils : request
  27. 49.

    if mime_type == Mime[:xml] # Rails 5 removed #deep_munge and

    replaced it with #normalize_encode_params munger = defined?(Request::Utils) ? Request::Utils : request params = Hash.from_xml(request.body.read) || {} data = munger.normalize_encode_params(params) request.body.rewind if request.body.respond_to?(:rewind) data.with_indifferent_access else false end rescue Exception # XML code block errors logger(env).debug "Error occurred while parsing request parameters.\nContents: \n\n#{request.raw_post}" raise ActionDispatch::ParamsParser::ParseError end def content_type_from_legacy_post_data_format_header(env) if env['HTTP_X_POST_DATA_FORMAT'].to_s.downcase == 'xml' Mime[:xml] end end def logger(env) env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr) end end end
  28. 50.

    require 'active_support' require 'active_support/core_ext/hash/conversions' require 'action_dispatch' require 'action_dispatch/http/request' module ActionPack

    class XmlParser def self.register original_parsers = ActionDispatch::Request.parameter_parsers ActionDispatch::Request.parameter_parsers = original_parsers.merge(Mime[:xml].symbol => self) end def self.call(raw_post) Hash.from_xml(raw_post) || {} end end end
  29. 51.

    require 'active_support' require 'active_support/core_ext/hash/conversions' require 'action_dispatch' require 'action_dispatch/http/request' module ActionPack

    class XmlParser def self.register original_parsers = ActionDispatch::Request.parameter_parsers ActionDispatch::Request.parameter_parsers = original_parsers.merge(Mime[:xml].symbol => self) end def self.call(raw_post) Hash.from_xml(raw_post) || {} end end end
  30. 53.
  31. 54.
  32. 58.

    class Admin::UsersController < ApplicationController def update @user = User.find(params[:id]) if

    user.update_attributes(params[:user]) redirect_to root_path else render 'edit' end end end
  33. 59.

    <%# app/views/admin/users/edit.html.erb %> <%= form_for @user do |f| %> <%=

    f.text_field :name %> <%= f.password_field :password %> <%= f.check_box :admin %> <% end %>
  34. 60.

    class User < ApplicationRecord attribute :name, :string attribute :password, :string

    attribute :admin, :boolean attr_accessible :name, :password, :admin end
  35. 61.

    class UsersController < ApplicationController def update @user = current_user if

    user.update_attributes(params[:user]) redirect_to root_path else render 'edit' end end end
  36. 62.

    <%# app/views/users/edit.html.erb %> <%= form_for @user do |f| %> <%=

    f.text_field :name %> <%= f.password_field :password %> <% end %>
  37. 63.
  38. 65.

    class User < ApplicationRecord attribute :name, :string attribute :password, :string

    attribute :admin, :boolean attr_accessible :name, :password, :admin, as: :admin attr_accessible :name, :password end
  39. 66.

    class Admin::UsersController < ApplicationController def update @user = User.find(params[:id]) if

    user.update_attributes(params[:user], as: :admin) redirect_to root_path else render 'edit' end end end
  40. 67.

    class Admin::UsersController < ApplicationController def update @user = User.find(params[:id]) if

    user.update_attributes(params[:user], as: :admin) redirect_to root_path else render 'edit' end end end
  41. 69.

    class Admin::UsersController < ApplicationController def update @user = User.find(params[:id]) if

    user.update_attributes(user_params) redirect_to root_path else render 'edit' end end private def user_params params.require(:user). permit(:name, :password, :admin) end end
  42. 70.
  43. 75.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] } # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title' } end end
  44. 76.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] } # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title' } end end
  45. 77.

    post :create, post: { title: 'Title', tags: [] } #

    => Not the actual implementation @request = TestRequest.new @request.params = { post: { title: 'Title', tags: [] } } controller = UsersController.new controller.request = request controller.create
  46. 78.

    post :create, post: { title: 'Title', tags: [] } #

    => Not the actual implementation @request = TestRequest.new @request.params = TestRequest.encode_params( post: { title: 'Title', tags: [] } ) controller = UsersController.new controller.request = request controller.create
  47. 79.

    post :create, post: { title: 'Title', tags: [] } #

    => @request = TestRequest.new @request.params = TestRequest.encode_params( post: { title: 'Title', tags: [] } ) controller = UsersController.new controller.request = request controller.create
  48. 80.
  49. 81.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] }, as: :json # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title', "tags" => [] } end end
  50. 82.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] }, as: :json # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title', "tags" => [] } end end
  51. 83.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] }, as: :json # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title', "tags" => [] } end end
  52. 84.

    class UsersControllerTest < ActionController::TestCase test "#create" do post :create, post:

    { title: 'Title', tags: [] }, as: :json # Rails 4.2 # => "post" => { "title" => 'Title', "tags" => [] } # Rails 5.0 # => "post" => { "title" => 'Title', "tags" => [] } end end
  53. 88.

    params = ActionController::Parameters.new(name: 'Rafael') params.to_h # => {} params.to_unsafe_h #

    => { 'name' => 'Rafael' } params.permit(:name).to_h # => { 'name' => 'Rafael' }
  54. 89.

    params = ActionController::Parameters.new(name: 'Rafael') params.to_h # => {} params.to_unsafe_h #

    => { 'name' => 'Rafael' } params.permit(:name).to_h # => { 'name' => 'Rafael' }
  55. 90.

    params = ActionController::Parameters.new(name: 'Rafael') params.to_h # => {} params.to_unsafe_h #

    => { 'name' => 'Rafael' } params.permit(:name).to_h # => { 'name' => 'Rafael' }
  56. 91.

    params = ActionController::Parameters.new(name: 'Rafael') params.to_h # => {} params.to_unsafe_h #

    => { 'name' => 'Rafael' } params.permit(:name).to_h # => { 'name' => 'Rafael' }
  57. 99.
  58. 106.
  59. 109.
  60. 110.
  61. 111.
  62. 112.
  63. 113.
  64. 115.
  65. 120.
  66. 128.