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:
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.
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.
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 beta2, 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
We need to focus on the Gemfile first. Start by changing that ~> 3.2
twiddle-wakka to our target version of ~> 4.2.0.beta2
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.beta2' # 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.
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.
config.filter_parameters
from application.rb to config/initializers/filter_parameter_logging.rbconfig.assets.precompile
and config.assets.version
have moved to config/initializers/assets.rbRails.application
singleton resource vs MyApp::Application
constant.config.active_support.test_order = :random
added in test environment file.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.
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"
match
methods in favor of get
verb method.test/functional
folder to test/controllers
test/unit
folder to test/models
test/mailers
folder and moved all my mailer tests from units to it.ActionController::IntegrationTest
to ActionDispatch::IntegrationTest
.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.
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)
The #deliver
method is deprecated and will be removed in Rails 5. Now that Rails 4.2 has ActiveJob
the preferred way to deliver emails via it would be to call #deliver_later
or #deliver_now
. Since the default queue adapter for ActiveJob is :inline
, just go ahead and switch things to the deliver later method. You can then change your queue adapter and create workers later on if needed.
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:
config.action_mailer.show_previews
to config/environments/development.rb file.test/mailers/previews
directory.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.
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.
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.
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!