March 30, 2015
Moving Contactually to Rails 4.2
A brief history of Contactually's usage of Rails
When we first started building Contactually three years ago, we were running Rails 3.2.8 and felt pretty proud to be on the bleeding edge. Through the years, we dutifully upgraded through all of the subsequent point releases, but never made the major version jump to 4.x.
Increasingly, the gems we use to run the project stopped supporting 3.x, and we soon realized it was time to get off that sinking ship while we still could. We didn't want to fall too far behind the major versions of Rails, or our most heavily-used gems. We were losing out on potential performance gains, new features, and community support -- and truthfully, we just like playing with the latest and greatest. Most importantly though, the biggest reason for us to upgrade was simply to stay at least one major version away from obsolescence.
For those of you who still need to move up to Rails 4.2, here's what that transition looks like, circa February 2015.
The process
We dipped our toes in the water by first updating the required version of Rails in our Gemfile. While we were at it, we also upgraded the Mongoid version from 3.1.3 to 4.0.0. After that, it was a simple bundle install to upgrade our Gemfile.lock.
Our old configuration files needed to be updated before we could even see how many of our tests were failing, so we started a new, empty Rails 4 project to compare them. The first thing we noticed was that the blank project generally used Rails.application and the only place
OUR_APP_NAME
was referenced was module
OUR_APP_NAME
in
config/application.rb
. However, throughout our environment and
config.ru
files we were using
OUR_APP_NAME::Application
, so we changed
OUR_APP_NAME::Application
to
Rails.application
to be consistent.
There were also a bunch of configuration options that moved from
config/application.rb
to their own initializers, or were simply removed altogether. We were using
config.whiny_nils
,
config.best_standards_support
, and
config.assets.compress
, all of which were deprecated in Rails 4.2. Asset related configs are now located in
config/initializers/assets.rb
and parameter filtering has moved to
config/initializers/filter_parameter_logging.rb
.
Our routes defined by the match method needed to be updated to either:
- explicitly use the http verb, or
- add the via option to list which verbs should match this route.
Since 4.0, Rails has used PATCH as the primary verb for the update action when using resource, but as it still creates a PUT route, we were okay there.
Another concern was keeping out users logged in across our upgrade. Fortunately, the Rails developers have made transitioning to more secure cookies pretty painless.
- In Rails 4, cookies are encrypted, whereas in Rails 3 they're merely signed (although the Rails developers anticipated this). By leaving in our secret_token, Rails 4 can read the old cookies, and then use the session_store key to encrypt them.
- Prior to Rails 4.1, cookies were serialized via Marshal, but from Rails 4.1 on the default is JSON. Thankfully, setting the serializer to
:hybrid
will handle unserializing a Marshalled cookie, and then serialize things into :json
. Marshal is a bit faster, but also more dangerous.
Upgrading Mongoid also brought some major changes that weren't always backwards compatible. First, the configuration option for allowing dynamic fields moved from actually being a configuration option to being a module included in each model as needed with
include Mongoid::Attributes::Dynamic
.
Next, our default scopes had to change to Procs -- we had to monkey patch BSON::ObjectId for our Mongoid ID serialization.
[box type="shadow"]
module BSON
class ObjectId
def as_json(options = {})
to_s
end
end
end
[/box]
Rails 4 changed the has_many handles :conditions, :order, :uniq, ... well, really any option that changes the SQL query for the assocication. Now those strings options are Procs, using the actual
ActiveRecord::QueryMethods
methods. So lines like:
[box type="shadow"]
has_many :buckets, :order => "LOWER('name') ASC"
[/box]
become more like:
[box type="shadow"]
has_many :buckets, -> { order("LOWER('name') ASC") }
[/box]
Rails' view helpers
:confirm
and
:disable_with
options had to be moved inside a hash with a key of
:data
. So...
=submit_tag 'Delete', :confirm => "Are you absolutely sure?", :disable_with => "Deleting..."
... becomes :
=submit_tag 'Delete', :data => { :confirm => "Are you absolutely sure?", :disable_with => "Deleting..." }
Another issue was strong_params. As we started to make the required methods in our various controllers, we ended up with a decent amount of duplication. We DRY'ed those up by putting them in separate ActiveSupport::Concern modules, and included the ones required for each controller.
There, that wasn't so bad
We then went through our usual QA process, which includes deploying to our staging server. Doing so revealed our asset pipeline wasn't configured correctly for our static assets.
config.serve_static_files
had to be changed from
true
to
ENV['RAILS_SERVE_STATIC_FILES'].present?
-- which is false for production and staging (since we use a CDN to serve static assets), but true for our local development environments. There were a few other odds and ends which really speaks more to the importance of thorough testing, as these problems can pop up even with point releases. Overall, as far as Rails upgrades go, this wasn't as badas it could have been thanks to the clean upgrade paths provided by the Rails developers. Despite so many moving pieces, it was still a lot easier than previous major upgrades.
Now we're set to do it all over again with Rails 5 in the fall.