Upgrading Rails to 4.2.x and Ruby to 2.2.x — an end to end Example


In this post, I have recorded my experiences when upgrading my hobby Web Application, a ProvisioningEngine front end from Rails 4.1.x to Rails 4.2.x and ruby from 2.1.x to 2.2.x. The upgrade went relatively smoothly, apart from an additional gem needed (‘test-unit’) and a test failure due to changes in the Rails Url helpers.

v1: 2016-01-26: original post
v2: 2016-02-13: added a caveats section at the end (just before the summary) and added two rspec caveats with respect to the exception handling

Upgrading Rails and Ruby

Prerequisites

  • In this post, I assume that rbenv is installed. If you need to install rbenv, find the instructions here. Other options are possible (e.g. RVM), but they are out of scope for this blog post.

Preparation steps:

  • Better do not upgrade unless you have good coverage of automated tests of your application which all pass.
    • In my case, I have a small app with about ~200 rspec tests and an in-built mock that allows me to perform the tests independently of the backend systems I integrate with. Perform the tests after each major step.
  • Create a backup of your system.

Upgrade steps:

  • (recommended) Update rails to latest minor version and perform the automated tests. In my case I have updated rails from 4.1.4 to 4.1.14, and check that the tests are still O.K.
  • (optional, depending on the current ruby version and your target rails version) Upgrade ruby to the latest stable version and perform the automated tests. In my case, I have updated ruby from 2.1.3 to 2.2.4. Since our target rails version 4.2.x supports older versions of ruby, this step was optional. However, it will be mandatory later on, when we upgrade rails to 5.0, so I have performed it now.
  • (mandatory) Now we can upgrade rails to 4.2.x (you can find all rails versions here). I have decided to specify version >~ 4.2.4 in the Gemfile and we will see that the latest available 4.2.x version is fetched (4.2.5.1). Also, it is not clear yet, whether this version is supported by travisCI.
  • (mandatory) resolve issues and handle deprecation warnings

Cleanup steps:

  • (mandatory) save and commit changes to the SW repository
  • (mandatory) integration tests and merge to master

Step 0: take backup and perform automated tests:

Perform your automated tests and confirm that they are successful. In my case (I am using rspec), this means:

$ bundle exec rspec -f d spec/requests/provisioningobjects_spec.rb
...
Finished in 40.04 seconds
196 examples, 0 failures

I am a lazy guy, so I have created a VMware Snapshot in stead of performing a backup, which helps me to go back within two minutes. Once the upgrade has proven to be successful, I will remove the snapshot in order to retain the full performance. In addition, I have made sure the SW is committed and pushed to my GitHub repository.

Step 1: update rails to the latest stable minor version:

If you are using git, I recommend to create a new branch, e.g.:

$ git checkout -b upgrade_branch

You may need to save your work before with git add/git commit or save away current changes for later use with git stash.

In the application’s root folder, edit the Gemfile and change the rails version (in my case from 4.1.4 to 4.1.14):

# Gemfile
gem 'rails', '4.1.14'

Now repeat the automated tests and check the results. If successful, commit and save your work e.g. with git:

git add .; git commit -am "updated rails 4.1.4 -> 4.1.14"; git push

Step 2: update ruby

Step 2.1 update rbenv, so you get the full list of ruby versions:

cd ~/.rbenv/
git pull
rbenv install --list

Step 2.2: install new ruby version

Install an available ruby version:

rbenv install 2.2.4
ruby -v

The last command will still show the old version. We still need to tell rbenv to use the new version. Since I am running a productive rails server on the host, I do not want to change the global setting, so I am specifying an app-local version, which will be written into .ruby-version:

rbenv versions
rbenv local 2.2.4
rbenv versions
ruby -v

Now ruby is updated.

Step 2.3: re-install rails and other gems

For this version of ruby, rails and the other gems are not yet installed. Therefore the following error message is expected:

$ rails -v
rbenv: rails: command not found

The `rails' command exists in these Ruby versions:
 2.1.3

Since I have a working project with a Gemfile, we can install bundle first and install rails by performing a bundle update within the project’s root folder:

gem install bundle
bundle update

The latter command will install all gems specified in the Gemfile, inclulding the rails gem. In my case, I had following output:

provisioningengine@ProvisioningEngine:~/ProvisioningEnginev0.5.15_testfolder$ gem install bundle
Fetching: bundler-1.11.2.gem (100%)
Successfully installed bundler-1.11.2
Fetching: bundle-0.0.1.gem (100%)
Successfully installed bundle-0.0.1
Parsing documentation for bundler-1.11.2
Installing ri documentation for bundler-1.11.2
Parsing documentation for bundle-0.0.1
Installing ri documentation for bundle-0.0.1
Done installing documentation for bundler, bundle after 6 seconds
2 gems installed
provisioningengine@ProvisioningEngine:~/ProvisioningEnginev0.5.15_testfolder$ bundle install
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Installing rake 10.5.0
Installing i18n 0.7.0
Installing json 1.8.3 with native extensions
Installing minitest 5.8.4
Installing thread_safe 0.3.5
Installing builder 3.2.2
Installing erubis 2.7.0
Installing rack 1.5.5
Installing mime-types 2.99
Installing arel 5.0.1.20140414130214
Installing addressable 2.4.0
Installing execjs 2.6.0
Installing sass 3.2.19
Installing mini_portile2 2.0.0
Installing ffi 1.9.10 with native extensions
Installing choice 0.2.0
Installing coffee-script-source 1.10.0
Installing thor 0.19.1
Installing cookiejar 0.3.0
Installing daemons 1.2.3
Installing tilt 1.4.1
Installing diff-lcs 1.2.5
Installing eventmachine 1.0.9.1 with native extensions
Installing http_parser.rb 0.6.0 with native extensions
Installing hike 1.2.3
Installing multi_json 1.11.2
Installing pg 0.15.1 with native extensions
Using bundler 1.11.2
Installing ruby-graphviz 1.2.2
Installing rails_serve_static_assets 0.0.4
Installing rails_stdout_logging 0.0.4
Installing rspec-core 2.13.1
Installing rspec-mocks 2.13.1
Installing rubyzip 0.9.9
Installing websocket 1.0.7
Installing spork 1.0.0rc4
Installing sqlite3 1.3.11 with native extensions
Installing rdoc 4.2.1
Installing tzinfo 1.2.2
Installing rack-test 0.6.3
Installing rack-protection 1.5.3
Installing mail 2.6.3
Installing autoprefixer-rails 6.3.1
Installing uglifier 2.7.2
Installing nokogiri 1.6.7.2 with native extensions
Installing childprocess 0.5.9
Installing coffee-script 2.4.1
Installing figaro 1.1.1
Installing rspec-expectations 2.13.0
Installing em-socksify 0.3.1
Installing sprockets 2.12.4
Installing rails_12factor 0.0.2
Installing sdoc 0.4.1
Installing activesupport 4.1.14
Installing sinatra 1.4.7
Installing bootstrap-sass 3.3.5
Installing xpath 2.0.0
Installing selenium-webdriver 2.35.1
Installing em-http-request 1.1.3
Installing actionview 4.1.14
Installing activemodel 4.1.14
Installing delayed_job 4.1.1
Installing factory_girl 4.2.0
Installing jbuilder 2.4.0
Installing capybara 2.1.0
Installing actionpack 4.1.14
Installing activerecord 4.1.14
Installing actionmailer 4.1.14
Installing railties 4.1.14
Installing kaminari 0.16.3
Installing sprockets-rails 2.3.3
Installing delayed_job_active_record 4.1.0
Installing delayed_job_web 1.2.5
Installing rails-erd 1.4.5
Installing seed_dump 3.2.4
Installing coffee-rails 4.0.1
Installing factory_girl_rails 4.2.0
Installing jquery-rails 3.1.4
Installing respond-rails 1.0.1
Installing rspec-rails 2.13.1
Installing rails 4.1.14
Installing sass-rails 4.0.5
Installing turbolinks 2.5.3
Installing spork-rails 4.0.0
Installing jquery-turbolinks 2.1.0
Bundle complete! 30 Gemfile dependencies, 85 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
  Post-install message from ruby-graphviz:

Depending on your version of ruby, you may need to install ruby rdoc/ri data:

<= 1.8.6 : unsupported
 = 1.8.7 : gem install rdoc-data; rdoc-data --install
 = 1.9.1 : gem install rdoc-data; rdoc-data --install
>= 1.9.2 : nothing to do! Yay!

Post-install message from capybara:
IMPORTANT! Some of the defaults have changed in Capybara 2.1. If you're experiencing failures,
please revert to the old behaviour by setting:

    Capybara.configure do |config|
      config.match = :one
      config.exact_options = true
      config.ignore_hidden_elements = true
      config.visible_text_only = true
    end

If you're migrating from Capybara 1.x, try:

    Capybara.configure do |config|
      config.match = :prefer_exact
      config.ignore_hidden_elements = false
    end

Details here: http://www.elabs.se/blog/60-introducing-capybara-2-1

Step 2.4: check ruby installation

The I tried to perform the automated tests, but there are some missing dependencies:

$ bundle exec rspec -f d spec/requests/provisioningobjects_spec.rb
/home/provisioningengine/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/activesupport-4.1.14/lib/active_support/dependencies.rb:247:in `require': cannot load such file -- test/unit/assertions (LoadError)
        from /home/provisioningengine/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/activesupport-4.1.14/lib/active_support/dependencies.rb:247:in `block in require'

8-((

What the heck is going on here?

Following the hints in the issue descriptions here and here, I have added the gem ‘test-unit’ to the test group of the Gemfile:

# Gemfile
group :test do
  # ...
  gem 'minitest'
  gem 'test-unit'
end

and performed

bundle install

This has done the job:

$ bundle exec rspec -f d spec/requests/provisioningobjects_spec.rb
...(lots of output and debugs omitted here)...
Finished in 40.04 seconds
196 examples, 0 failures

The procedure might also work without the gem ‘minitest’. In my case it was already installed, so I have not tried without.

Now is a good time to commit the change in git:

$ git add.; git commit -a

And I have specified:

Updated ruby to 2.2.4, re-installed all gems; rails v4.1.14

all 196 tests successful in simulation mode

Step 3: upgrade rails to v 4.2.x

In the Gemfile, I have exchanged the rails line by:

# Gemfile
gem 'rails', '~> 4.2.4'

Then:

$ bundle update
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Resolving dependencies............................................................................
Using rake 10.5.0
Using i18n 0.7.0
Using json 1.8.3
Using minitest 5.8.4
Using thread_safe 0.3.5
Using builder 3.2.2
Using erubis 2.7.0
Using mini_portile2 2.0.0
Installing rack 1.6.4 (was 1.5.5)
Using mime-types 2.99
Installing arel 6.0.3 (was 5.0.1.20140414130214)
Using addressable 2.4.0
Using execjs 2.6.0
Using sass 3.2.19
Using bundler 1.11.2
Using ffi 1.9.10
Using choice 0.2.0
Using coffee-script-source 1.10.0
Using thor 0.19.1
Using cookiejar 0.3.0
Using daemons 1.2.3
Using tilt 1.4.1
Using diff-lcs 1.2.5
Using eventmachine 1.0.9.1
Using http_parser.rb 0.6.0
Using hike 1.2.3
Using multi_json 1.11.2
Using pg 0.15.1
Installing power_assert 0.2.7 (was 0.2.2)
Using ruby-graphviz 1.2.2
Using rails_serve_static_assets 0.0.4
Using rails_stdout_logging 0.0.4
Using rspec-core 2.13.1
Using rspec-mocks 2.13.1
Using rubyzip 0.9.9
Using websocket 1.0.7
Using spork 1.0.0rc4
Using sqlite3 1.3.11
Using rdoc 4.2.1
Using tzinfo 1.2.2
Using nokogiri 1.6.7.2
Using rack-test 0.6.3
Using rack-protection 1.5.3
Using mail 2.6.3
Using autoprefixer-rails 6.3.1
Using uglifier 2.7.2
Using childprocess 0.5.9
Using coffee-script 2.4.1
Using figaro 1.1.1
Using rspec-expectations 2.13.0
Using em-socksify 0.3.1
Using sprockets 2.12.4
Installing test-unit 3.1.7 (was 3.0.8)
Using rails_12factor 0.0.2
Using sdoc 0.4.1
Installing activesupport 4.2.5.1 (was 4.1.14)
Installing loofah 2.0.3
Using xpath 2.0.0
Using sinatra 1.4.7
Using bootstrap-sass 3.3.5
Using selenium-webdriver 2.35.1
Using em-http-request 1.1.3
Installing rails-deprecated_sanitizer 1.0.3
Installing globalid 0.3.6
Installing activemodel 4.2.5.1 (was 4.1.14)
Using delayed_job 4.1.1
Using factory_girl 4.2.0
Using jbuilder 2.4.0
Installing rails-html-sanitizer 1.0.3
Using capybara 2.1.0
Installing rails-dom-testing 1.0.7
Installing activejob 4.2.5.1
Installing activerecord 4.2.5.1 (was 4.1.14)
Installing actionview 4.2.5.1 (was 4.1.14)
Using delayed_job_active_record 4.1.0
Using delayed_job_web 1.2.5
Using rails-erd 1.4.5
Using seed_dump 3.2.4
Installing actionpack 4.2.5.1 (was 4.1.14)
Installing actionmailer 4.2.5.1 (was 4.1.14)
Installing railties 4.2.5.1 (was 4.1.14)
Using kaminari 0.16.3
Using sprockets-rails 2.3.3
Using coffee-rails 4.0.1
Using factory_girl_rails 4.2.0
Installing jquery-rails 4.1.0 (was 3.1.4)
Using respond-rails 1.0.1
Using rspec-rails 2.13.1
Installing rails 4.2.5.1 (was 4.1.14)
Using sass-rails 4.0.5
Using turbolinks 2.5.3
Using spork-rails 4.0.0
Using jquery-turbolinks 2.1.0
Bundle updated!

We can see in the log that rails has been upgraded to the latest available 4.2 version at time of writing (4.2.5.1). Now let us test again. This time we get lots of DEPRECATION WARNINGs and an error:

bundle exec rspec -f d spec/requests/provisioningobjects_spec.rb
...
DEPRECATION WARNING: Calling URL helpers with string keys controller, action is deprecated. Use symbols instead. (called from _app_views_customers__sidebar_html_erb__545434719134816655_70018526941280 at /home/provisioningengine/ProvisioningEnginev0.5.15_testfolder/app/views/customers/_sidebar.html.erb:8)
 should have link to 'New Customer' (FAILED - 1)
Failures:

  1) On target solution 'Environment2_V7R1' index should have link to 'New Customer'
     Failure/Error: expect(page).to have_link( "New #{obj}", href: new_provisioningobject_path(obj) + '?per_page=all') #unless obj == "Site"
       expected #has_link?("New Customer", {:href=>"/customers/new?per_page=all"}) to return true, got false
     # ./spec/requests/provisioningobjects_spec.rb:1246:in `block (5 levels) in <top (required)>'

Finished in 17.13 seconds
68 examples, 1 failure

Failed examples:

rspec ./spec/requests/provisioningobjects_spec.rb:1241 # On target solution 'Environment2_V7R1' index should have link to 'New Customer'

Step 4.1: resolving the introduced Url-Helper issue

Okay, there is more work to be done. Maybe I should have tried with ‘bundle install’ first, not with ‘bundle update’, which was updating many gems at once? We could go back with a git stash and try again with bundle install. However, let us quickly see, if there is a quick solution:

The DEPRECATION WARNING is related with the problem. It points to

/home/provisioningengine/ProvisioningEnginev0.5.15_testfolder/app/views/customers/_sidebar.html.erb:8

and there we find:

<li><%= link_to 'New Customer', new_customer_path(@params) %></li>

That is causing the errored test:

expected #has_link?("New Customer" ...

So let us try to understand the DEPRECATION WARNING. By guessing from the error message, I have replaced the @params assignment in

class ProvisioningobjectsController < ApplicationController
def index
  @params = params

by

  @params = {"per_page"=>"all", "controller"=>"customers", "action"=>"index", "target_id"=>nil}

after I had seen that a puts @params.inspect had returned {“per_page”=>...}. Surprisingly, after this change the test was successful with the the DEPRECATION WARNING still present. The reasons seems to be that params is an ActionController::Parameters while the literal {...} is a Hash.

The remaining warning was removed by replacing the keys by symbols:

  @params = {:per_page=>"all", :controller=>"customers", :action=>"index", :target_id=>nil}

Since I do not want to care about the actual parameters, I replaced this by the more general code:

class ProvisioningobjectsController < ApplicationController
def index
  @params = {}
  params.each do |key,value|
     @params[key.parameterize.underscore.to_sym] = value
  end

This will convert any ActionController::Parameters parameters by a Hash with symbol keys.

Even more surprisingly, now all 196 test cases are successful:

$ bundle exec rspec -f d spec/requests/provisioningobjects_spec.rb
...
Finished in 42.56 seconds
196 examples, 0 failures

Step 4.2: resolving DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`

During the test, we have seen other deprecation warnings like follows:

DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (called from validate at /home/provisioningengine/ProvisioningEnginev0.5.15_testfolder/app/models/user.rb:15)

Let us resolve this as well. The waring is quite explicit and points to a line containing:

@site = Site.find(record.site)

This can easily be resolved by exchanging it by:

@site = Site.find(record.site.id)

Done and works.

Step 4.3: resolving DEPRECATION WARNING: renamed configuration option

Now let us tackle the last remaining deprecation warning:

DEPRECATION WARNING: The configuration option `config.serve_static_assets` has been renamed to `config.serve_static_files` to clarify its role (it merely enables serving everything in the `public` folder and is unrelated to the asset pipeline). The `serve_static_assets` alias will be removed in Rails 5.0. Please migrate your configuration files accordingly. (called from block in <top (required)> at /home/provisioningengine/ProvisioningEnginev0.5.15_testfolder/config/environments/test.rb:16)

We just need to exchange the line 16 in config/environments/test.rb

config.serve_static_assets  = true

by

config.serve_static_files  = true

and we are done with all deprecation warnings.

Step 5: cleanup: save changes, additional test with travisCI and remove snapshot

Finally, this is a good time to commit the change to git and to merge the branch to the original branch (“development” branch) and push it to GitHub. In my case, this has triggered an additional test on travisCI (if you have no access, try travisCI home).

2016.01.26-23_59_14-hc_001

Success.

Once real live tests with real provisioned legacy end systems have proven to be successful as well, we will merge the development branch to the master branch and remove the VMware snapshot we had taken before we started the upgrade.

Caveats

  • In the new rspec version (rspec-core: 3.4.2), the exception handling seems to have changed: if there is an exception/abort in the rspec script or in the called code, the rspec just stops, but it does not indicate an error. A short google session did not lead to any results. I need to find a solution or a workaround for this.
  • rspec: begin/rescue block does not work as (I had) expected: if you write a begin/rescue block like
begin
  something_raising_an_exception
rescue
  some_other_code
end

then the rescue is ignored, the abort message is printed and the rspec test suite is stopped.

Summary

For my hobby Web Application, a ProvisioningEngine front end,  I could upgrade Rails and Ruby relatively smoothly within an afternoon+evening session (including the documentation on my blog). There only was a missing dependency ‘test-unit’ and a changed behavior of the url helpers, which has forced me to convert the Url helper parameter from ActionController::Parameters class ) to Hash {...} class with the same content. In addition, the Hash keys had to be converted from "String" to :symbol in order to get rid of the corresponding DEPRECATION WARNING. After that, all ~200 rspec tests were successful.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s