Upgrade to Rails 4

Note: this post was published in an unfinished form to help support a recent Rails upgrading meetup. It will be updated over the next few weeks. Thanks for stopping by and I do respond to comments, so feel free to ask questions.

Most blog posts promising “Upgrade <insert whatever here>!” imply a trouble-free, straightforward procedure which, if followed 1-2-3 results in your shiny new application, environment, software, whatever.

If only it were always that easy!

Whenever I try upgrading 1-2-3, invariably it’s more like 1-2-2.1-2.3-3.1415-(minus)1-4-4.6692-5-…

There’s always something.

The older or larger my application, the more pitfalls I seem to stumble into.

I’m willing to admit this is likely a consequence of my own inattention. I don’t always make the time to keep everything completely up to date.

So when it’s time to make a big upgrade, big problems!

Upgrade infrastructure first

Rails 3 is boring and just so 2010. So we want Rails 4.

How to make this happen without killing ourselves?

Well, we’ll do it stepwise (1-2-3), but add all those missing steps. Like filling in the potholes before you flatten a tire.

Here is what works for me:

  1. If you’re using rvm or rbenv, update those.
  2. Update to Ruby 2.0.0-p353 (minimum). Consider updating to Ruby 2.1.
  3. Create a Rails 4 rvm gemset (or rbenv equivalent) based on Ruby 2.0. Note: I use rvm to control my ruby environment, in contrast to Bundler, which manages Ruby dependencies.
  4. Update rubygems. This is really important, many other gems now depend on a recent version of rubygems.
  5. Update Bundler to the most recent version (which depends on an up-to-date rubygems).
  6. git checkout an update branch for your application, and switch to your new Ruby 2.0 gemset.
  7. After these updates, ensure your test suite still passes, fixing anything which fails.
  8. Update your Rails gem to 3.2.14. Stepping up to Rails 4 from Rails 3.2.14 is not as difficult as getting from Rails 3.0 to Rails 3.1.
  9. Make a list of out of date gems using bundle outdated. It’s not strictly necessary to update all your gems before updating Rails, but doing so can help quite a bit.

Updating gems and Gemfile

When updating gems, there are two approaches. The first is bundle update which attempts to update everything all at once. It can work. If that fails, bundle update gemname one at a time. Either way, ensure all your tests pass after each round of updating. If you find a major update of RSpec, minitest, Capybara or Cucumber, you should expect at least some failures.

If you can run bundle update, and it successfully upgrades to Rails 4, and all your tests pass, go home you’re done!

Gems one at a time

If you feel more comfortable updating gems one at a time, or if bundle update just doesn’t work, here’s how:

$ bundle outdated
Updating git://github.com/doolin/haml-kramdown.gitFetching gem metadata
from http://rubygems.org/.........
Fetching gem metadata from http://rubygems.org/..
Resolving dependencies...

Outdated gems included in the bundle:
  * arel (4.0.0 > 3.0.2)  * builder (3.2.0 > 3.0.4)
  * coffee-rails (4.0.0 > 3.2.2)
  * i18n (0.6.4 > 0.6.1)
  * jquery-rails (2.2.1 > 2.1.4)
  * rack (1.5.2 > 1.4.5)
  * rdoc (4.0.1 > 3.12.2)
  * sprockets (2.9.3 > 2.2.2)

Now pick a gem, say, rack and run bundle update rack.

Unpin gems in Gemfile

If your Gemfile has gems which are pinned to specific version, bundle update will not update those gems, and will not update gems which depends on those specific versions of such gems. For example, perhaps you have something like the following:

source "https://rubygems.org"
...
group :test do
  gem 'rspec', '2.12'
end
...

To upgrade rspec, you will need to “unpin” it.

If possible, unpin all the gems in your gemfile save the Rails gem.

Once all the above is accomplished, then it’s time to update Rails proper.

The obvious stuff

Most of this is covered elsewhere, but it’s worth going through again, as deprecations, warnings, and errors continually re-emerge.

Scopes

Scopes now prefer lambdas over hashes.

If you have a default scope, get that dealt with first. For example:

  • Old: default_scope :order => 'table.column DESC'
  • New: default_scope { order('table.column DESC' }

An easy change.

Upgrading a Rails Tutorial application

Older Rails tutorial applications will require upgrades of RSpec, Cucumber and FactoryGirl. None of these are difficult. Applying the gem updates in the following order should help:

  1. FactoryGirl
  2. RSpec
  3. Cucumber

Upgrade FactoryGirl

Read the [FactoryGirl documentation (https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md) carefully.

Some highlights:

In the spec files

Old:

Factory(:model)
Factory.next(:email)

New:

FactoryGirl.build(:model)
FactoryGirl.generate(:email) # sequence

In factories.rb

Old:

Factory(:user) do |u|
  u.name "Dave"
end

New:

FactoryGirl.define do
  factory :user do
    name "Dave"
  end
end

FactoryGirl.define do
  sequence :email do |n|
    "email #{n}"
  end
end

Updating Devise

After updating Devise, the new version crashed on a NoMethodError.

This is a result of a major API change in Devise, as documented here:
database_authenticatable

Check out commit c96e0b35eae on Portfolio Project Rails 4 branch for what the migration looks like:

@@ -1,15 +1,23 @@
 class DeviseCreateMembers < ActiveRecord::Migration
   def self.up
     create_table(:members) do |t|
-      t.database_authenticatable :null => false
-      t.recoverable
-      t.rememberable
-      t.trackable
+      ## t.database_authenticatable :null => false
+      t.string :email, :null => false, :default => ""
+      t.string :encrypted_password, :null => false, :default => ""
 
-      # t.confirmable
-      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
-      # t.token_authenticatable
+      #t.recoverable
+      t.string :reset_password_token
+      t.string :reset_password_sent_at
 
+      #t.rememberable
+      t.datetime :remember_created_at
+
+      #t.trackable
+      t.integer :sign_in_count, :default => 0
+      t.datetime :current_sign_in_at
+      t.datetime :last_sign_in_at
+      t.string :current_sign_in_ip
+      t.string :last_sign_in_ip
 
       t.timestamps
     end

Getting devise install ended up requiring a gem install devise -v
'3.0.0.rc'
similar to Rails 4 above.

This is a good technique: install the bare gems using gem, then let
bundler sort it out later.

Note: schema changes on the latest devise, as noted above, may
induce a desire to examine the state of the schema on Heroku. That’s not
difficult, just fire up a bash prompt on
heroku
with heroku run bash -a app_name and manually examine the schema
for devise.

Posted in rails | Leave a comment

Driving out belongs_to and has_many

Here’s one way to do it:

  1. Set up the model_spec.rb file first. We’re going to use recipes and users for an example
  2. Add associations to the models recipe.rb and user.rb
  3. Create the migration and apply it.

Wanna see some code? Click through…

Posted in testing | Tagged , | 1 Response

Devise users code kata

Devise is pretty spiffy, what with handling authentication 1-2-3. However, you still need to handle the users, and this code kata is exactly what you need.

The kata looks about like this:

  1. Do the Rails thing
  2. Do the Devise thing, install the views, etc.
  3. Install cucumber-rails
  4. Add session.feature
  5. Add session_steps.rb
  6. Add a resources :users (or resources :members or whatever) to config/routes.rb.
  7. Install rspec-rails
  8. Add controller spec
  9. Add routing spec
  10. Add view spec
  11. Iterate until everything passes

There’s a fair bit of code involved in this one…

Posted in rails | Tagged , , , , , | Leave a comment


dool.in