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

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.

Rafael França

April 26, 2017
Tweet

More Decks by Rafael França

Other Decks in Technology

Transcript

  1. 5

  2. 5

  3. 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
  4. 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
  5. 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
  6. 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. 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. 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. 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. 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. Long-running branch strategy 1. Create a branch 2. Make all

    changes necessary 3. Merge it 4. ?????? 5. Profit!
  12. 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
  13. 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'
  14. 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.
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. <%# 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 %>
  21. class User < ApplicationRecord attribute :name, :string attribute :password, :string

    attribute :admin, :boolean attr_accessible :name, :password, :admin end
  22. class UsersController < ApplicationController def update @user = current_user if

    user.update_attributes(params[:user]) redirect_to root_path else render 'edit' end end end
  23. <%# app/views/users/edit.html.erb %> <%= form_for @user do |f| %> <%=

    f.text_field :name %> <%= f.password_field :password %> <% end %>
  24. class User < ApplicationRecord attribute :name, :string attribute :password, :string

    attribute :admin, :boolean attr_accessible :name, :password, :admin, as: :admin attr_accessible :name, :password end
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. params = ActionController::Parameters.new(name: 'Rafael') params.to_h # => {} params.to_unsafe_h #

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

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

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

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