Ruby, JavaScript, Sass, iOS. Stinky Cheese & Beer Advocate. Working at CustomInk and loving it!

From Rails 3.2 to 4.2

Last week I set out to upgrade HomeMarks, a personal bookmarking project of mine. This application sat on a very recent upgrade to Rails 3.2. It is written as an API to both an iOS and HTML JavaScript interface. It is by no means huge and should represent a nominal service oriented application. Here are some stats:

  • 8 Models (450 LOC)
  • 11 Controllers (550 LOC)
  • 2 Mailers (50 LOC)
  • 8 Libraries (500 LOC)
  • Using Ruby 2.1.2.

The application is heavily tested with an emphasis on controller and integrations. It uses a standard Rails test setup with simple minitest-spec-rails usage. Integrations are done using the Capybara DSL with the Poltergeist driver. JavaScript tests use Konacha which leverage MochaJS and the Chai assertion library.

With the introductions out of the way let's do some fun upgrade work! Here is a step by step process of how I tackled the task and I hope you find it useful.

Grease The Wheels

The first step I always recommend for any Rails upgrade is to update your entire bundle with your current Rails semver as the only gem constraint. The goal here is to have no gem versions other than ~> 3.2 for Rails itself in your Gemfile - hence allowing Bundler to do all the work during your big 4.2 update. For this reason you should delete any explicit gem version specs and run bundle update.

During this time you will want to remove any non-essential gems as well. Great examples are test related gems. Spending time debugging any non-essential gem during an upgrade process is the worst time spent of all. Likewise, identify gems that are obsolete in your target upgrade. In my application, good examples were the mail_view and the quiet_assets gems. Hopefully your application has a tagging convention for notes when upgrading. At CustomInk, we use PENDING: [Rails4]... style comment tags.

Once done, update your master branch with this work and get that deployed. This is your new base for the push to Rails 4.2.

Make A Template

We want to reference a fresh 4.2 application as a guide. This will come in handy in many ways later on. During the time of this article Rails 4.2 was in beta1, so all examples will use that version. Please adjust your commands/examples as newer versions are released.

$ gem install rails 4.2.0.beta1
$ rbenv rehash
$ rails new myapp

The Big Bundle Update

We need to focus on the Gemfile first. Start by changing that ~> 3.2 twiddle-wakka to our target version of ~> 4.2.0.beta1 as our Rails gem version. Go through your notes and delete those obsolete gems too and make any adjustments to any pessimistic version if you noted any.

In Rails 4.2, there is no such thing as an assets group for gems. So I removed my group :assets and flattened my asset gems to the root of the Gemfile. I do suggest maintaining an comment and clustering your asset gems in your Gemfile as a way to keep them organized. Due to sass-rails being in beta, I ended up with something like this.

gem 'bcrypt-ruby'
gem 'pg'
gem 'rails', '~> 4.2.0.beta1' # PENDING: [Rails 4.2] Remove wakka.

# Assets
gem 'bourbon'
gem 'coffee-rails'
gem 'jquery-rails'
gem 'sass-rails', '~> 5.0.0.beta1' # PENDING: [Rails 4.2] Remove wakka.
gem 'uglifier'

Lastly, I find it extremely useful to delete the Gemfile.lock now. In my experience Bundler will have a much better time coping by allowing the entire dep graph to be rebuilt. Now go for the big update with a simple bundle install.

$ bundle install

If you run into any problems here, take them on one by one. In some cases you could be using a gem that does not optimistically include 4.x versions. Before proceeding, find each project's issue tracker and do a little research. If you encounter resistance, look broadly first and avoid hacking around it. This could be your time to shine and help with an open source pull request.

Once you are bundled, you are ready for the next step. Avoid the temptation to launch your application or running tests! You are no where near ready for that, so slow your roll. We still have some good work to do.

The Mimic Process

This is a great time to open the template application we created above. It is a very straightforward process to go through each of the application's files & folders and mimic the template within your own application.

In my experience I have seen a lot of pain in converting over config/environments files to the newer format. If you have never done so, I highly recommend following a simple practice that makes upgrading of these files easier in the future. Always keep the environment file largely untouched and add your configs to the bottom of each below a comment like this.

Rails.application.configure do

  # ... Rails generated ...

  # My Configs
  # ----------
  config.action_mailer.show_previews

end

This makes updating these files in the future go much quicker. Here are some general notes I had when doing my entire mimesis process.

Environments & Initializers

  • Removed config.filter_parameters from application.rb to config/initializers/filter_parameter_logging.rb
  • The config.assets.precompile and config.assets.version have moved to config/initializers/assets.rb
  • Many files now use the Rails.application singleton resource vs MyApp::Application constant.

Concerns Directories

I fully embraced the new concern directories. I created both app/controllers/concerns/.keep and app/models/concerns/.keep. I found there were a few files in my lib directory that were actually concerns. For example, I moved both my AuthenticatedSystem and RenderInvalidRecord modules to the controller concerns. This allowed me to remove any hacks I had for setting the auto load path on the lib directory too.

Bin Directory & Spring

I have been using both bundler bin stubs and Spring in all of my 3.2 applications. Now that Rails supports both the local bin directory and Spring in an integrated way, I wanted to follow the golden path of least resistance.

The first step was to blow away my entire bin directory and just copy over the one from the application template. These new bin files for rake and rails leverage the Spring preloader. Make sure to delete your local or global bundle config for installing bins too. Do this with bundle config --delete bin. Now, instead of having Bundler install bin stubs for every gem which could conflict with the preloader bins, we should be explicit on a per gem basis. For example, this would install the bins for guard.

$ bundle binstubs guard

Lastly, if you have never done so, change your shell's PATH to look for the local ./bin directory before any others. I do this after my rbenv initialization.

eval "$(rbenv init -)"
export PATH="./bin:$PATH"

Routes File

  • Removed my match methods in favor of get verb method.

New Test Directories

  • Renamed test/functional folder to test/controllers
  • Renamed test/unit folder to test/models
  • Created a test/mailers folder and moved all my mailer tests from units to it.
  • Renamed ActionController::IntegrationTest to ActionDispatch::IntegrationTest.

Framework Changes

Now is the time where you can start to run your tests and identify what needs to change. I recommend starting with the model tests and moving on from there. Though I am a big fan of Guard for automatic test runs, Rails now has a new option when using rake. Just pass the filename after the test argument. For example, this would run a single model test and since the new bin/rake file uses Spring, you can run this command over and over again very quickly.

$ rake test test/models/user_test.rb

Below are things I found while moving through my tests. I have organized them by framework. They are by no means comprehensive and your application may expose more differences between Rails 3.2 and 4.2.

ActiveRecord

You are going to see a lot of ArgumentError: Unknown key: ... errors. The reason is that Rails 4.0 now requires that scopes use a callable object such as a Proc or lambda. I saw these errors mostly on :order and the :readonly option arguments. Here are a few before/after examples.

# Old
has_many :foos, order: 'position'
has_many :bars, through: :foos, readonly: false, order: 'foos.position, bars.position'

# New
has_many :foos, -> { order('position') }
has_many :bars, -> { order('foos.position, bars.position').readonly(false) }, through: :foos

Seems the callable object has to be the second argument and you can easily chain the scopes within. I took a wild guess that the readyonly scope took an optional false argument and was handsomely rewarded.

I also got a few good ArgumentError: The provided regular expression is using multiline anchors (^ or $) ... errors. This was easy to fix by using the suggested \A and \z instead. Really happy to see the framework warning on this common errors when using regular expressions for validations.

I deleted all the attr_accesible declarations from my models and switched to strong parameters in the controllers. If you are new to strong parameters, check out this article which goes into great depth on the topic. Alternatively, you can start using strong parameters before you upgrade to Rails 4.x by using the backward compatible strong parameters gem. A great strategy to ease large application transitions. Thanks to Chris Mar for pointing out this approach to our team.

The class update_all no longer takes a second conditions argument. I always disliked methods that took two option hashes and this is a great change. For example:

# Old
Book.update_all({author: 'David'}, {title: 'Rails'})

# New
Book.where(title: 'Rails').update_all(author: 'David')

Lastly, the all method no longer takes finder options.

# Old
User.all(conditions:{email:'ken@metaskills.net'}).map(&:id)

# New
User.where(email:'ken@metaskills.net').all.map(&:id)

ActionMailer

Using _path url methods in mailers will now result in a DEPRECATION WARNING... cannot be used here as a full URL is required message. Unless you were manually augmenting these paths to have a host, this is a good thing and will keep developers from including partial URLs in mailers.

MailView is now fully integrated into ActionMailer. Read this article for full details. If you have never used MailView in a Rails 3 application, it allows you to develop your mails in the browser as if they were controller view. When moving from old MailView support to Rails 4.x usage, follow these steps:

  • Add config.action_mailer.show_previews to config/environments/development.rb file.
  • Moved previous mailer previews to new test/mailers/previews directory.

ActionPack

Partials can no longer have - hyphens in the filename. I had to change a few.

There is a new and better request forgery protection in Rails 4.x. Read the source link for full error messages and documentation. In my case, I had already redefined a protected protect_against_forgery? for a special controller and needed to only add skip_before_action :verify_authenticity_token to my filters to fully work around the security warning.

Asset Pipeline

The biggest issue that I had is that asset compilation no longer generates non-digest filenames for each asset. This github issue explains the rationale, but I do believe there are some corner cases where you do want to reference the non-digest asset filename.

If after careful examination you find yourself in need of this corner case, do not disable digests for all files. Instead install the non-stupid-digest-assets gem which allows you to whitelist specific asset files and thereby including a non-digest filename along with the fingerprinted filenames for said asset(s). I recommend putting the NonStupidDigestAssets.whitelist settings at the bottom of the new config/initializers/assets.rb file.

Testing

The Rails testing task strategy has changed a lot. By default now, when you run rake test all models, mailers, controllers and integrations are run in one collective suite run. If you are interested in learning how, read the testing.rake source. I also talked about how to add different directories to this process within a github issues under the minitest-spec-rails project where Mike Moore contributed a few helpful hints too.

The reason I mention this is that it is somewhat common to expect your Capybara enhanced integrations to run in a distinct process. Because of this, it is also common to see monkey patches to the ActiveRecord connection pool to support integration tests that leverage DB transactions. Depending on your setup, you may be required to make a few tweaks.

In Closing

Upgrading Rails applications used to be a pain! My recent upgrade only took two evenings of my spare time. In my opinion Rails 3.1 and up have become much more stable for both application and gem authors to leverage the framework. Thus making upgrades approachable. Keep your applications small and focused as a way to win the upgrade wars!

In closing, thanks for reading and I hope you found this information helpful. If you have any questions, feel free to ask in the comments. Cheers!