
Upgrade Rails from 3.2 to 4.0
This article is part of our Upgrade Rails series. To see more of them, click here .
A previous post covered some general tips to take into account for this migration. This article will try to go a bit more in depth. We will first go from 3.2 to 4.0, then to 4.1 and finally to 4.2. Depending on the complexity of your app, a Rails upgrade can take anywhere from one week for a single developer, to a few months for two developers.
- Preparations
- Ruby version
- Gems
- Config files (config/)
- Application code
- Models (app/models/)
- Controllers (app/controllers/)
- Active Support
- Tests
- Miscellaneous
- Next steps
1. Preparations
Before beginning with the upgrade process, we have some recommended preparations:
- Your Rails app should have the latest patch version before you move to the next major/minor version.
- You should have at least 80% test coverage unless you have a dedicated QA team.
- Follow a Git flow workflow to actively manage at least two environments: staging and production.
- Check your Gemfile.lock for incompatibilities by using RailsBump .
- Create a dual boot mechanism, the fastest way to do this is installing the handy gem next_rails .
For full details check out our article on How to Prepare Your App for a Rails Upgrade .
2. Ruby version
Rails 3.2.x is the last version to support Ruby 1.8.7. If you’re using Ruby 1.8.7, you’ll need to upgrade to Ruby 1.9.3 or newer. The Ruby upgrade is not covered in this guide, but check out this guide for more details on that.
3. Gems
You can add the aptly named rails4_upgrade gem to your Rails 3 project’s Gemfile and find gems which you’ll need to update:
➜ myproject git:(develop) ✗ bundle exec rake rails4:check
** GEM COMPATIBILITY CHECK **
+------------------------------------------+----------------------------+
| Dependency Path | Rails Requirement |
+------------------------------------------+----------------------------+
| devise 2.1.4 | railties ~> 3.1 |
| devise-encryptable 0.2.0 -> devise 2.1.4 | railties ~> 3.1 |
| friendly_id 4.0.10.1 | activerecord < 4.0, >= 3.0 |
| strong_parameters 0.2.3 | actionpack ~> 3.0 |
| strong_parameters 0.2.3 | activemodel ~> 3.0 |
| strong_parameters 0.2.3 | activesupport ~> 3.0 |
| strong_parameters 0.2.3 | railties ~> 3.0 |
+------------------------------------------+----------------------------+
Instead of going through your currently bundled gems or Gemfile.lock
manually,
you get a report of the gems you need to upgrade.
4. Config files
Rails includes the rails:update
task .
You can use this task as a guideline as explained thoroughly in
this post .
It will help you get rid of unnecessary code or monkey-patches in your config
files and initializers, specially if your Rails 3 app was running on Rails 2.
As an alternative, check out RailsDiff , which provides an overview of the changes in a basic Rails app between 3.2 and 4.0 (or any other source/target versions).
If you’re feeling adventurous, you can give this script a try. It attempts to apply this git patch (similar to the patch shown on RailsDiff) to your Rails app to migrate from 3.2 to 4.0. However, I don’t recommend this for complex or mature apps, as there will be plenty of conflicts.
- Thread-safe by Default
In Rails 4 applications, the threadsafe!
option will be enabled by default in production mode. The way to turn it off will be by setting config.cache_classes
and config.eager_load
to false
.
config.threadsafe!
is deprecated in favor of config.eager_load
which provides a more fine grained control on what is eager loaded.
This shouldn’t be much of a problem, unless the app (or one of its gems) is depending too much on thread safety.
Here is a great blogpost by Aaron Patterson explaining this change and it’s consequences:
config.threadsafe!: What does it do?
- Eagar loading
In Rails 4 eager loading is controlled by config.cache_classes
and config.eager_load
.
1) Set the eager load value as per the below warning in each environment
2) If you have not defined config.eager_load
then it will use config.cache_classes
value.
if config.eager_load.nil?
warn <<-INFO
config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
* development - set it to false
* test - set it to false (unless you use a tool that preloads your test environment)
* production - set it to true
INFO
config.eager_load = config.cache_classes
end
5. Application code
a. Models
- All dynamic finder methods except for
.find_by_...
are deprecated:
# before:
Authentication.find_all_by_provider_and_uid(provider, uid)
# after:
Authentication.where(provider: provider, uid: uid)
You can regain usage of these finders by adding the gem activerecord-deprecated_finders
- ActiveRecord scopes now need a lambda:
# before:
default_scope where(deleted_at: nil)
# after:
default_scope { where(deleted_at: nil) }
# before:
has_many :posts, order: 'position'
# after:
has_many :posts, -> { order('position') }
# before:
scope :fulfilled, where{ fulfilled_at != nil }
# after:
scope :fulfilled, -> { where{ fulfilled_at != nil }}
(Friendly reminder: beware when using default_scope )
- Protected attributes is deprecated, but you can still add the protected_attributes gem. However, since the Rails core team dropped its support since Rails 5.0, you should begin migrating your models to Strong Parameters anyway.
To do so, you will need to remove calls to attr_accessible
from your models,
and add a new method to your model’s controller with a name like user_params
or your_model_params
:
class UsersController < ApplicationController
def user_params
params.require(:user).permit(:name, :email)
end
end
Finally, change (most) references to params[:user]
to user_params
in your
controller’s actions. If the reference is for an update or a creation, like
user.update_attributes(params[:user])
, change it to user.update_attributes(user_params)
.
This new method permits using the name
and email
attributes of the user
model and disallows writing any other attribute the user model may have (like id
).
- ActiveRecord Observers were removed from the Rails 4.0 codebase and extracted into a gem. You can regain usage by adding the gem to your Gemfile:
gem 'rails-observers' # https://github.com/rails/rails-observers
As an alternative, you can take a look at the wisper gem , or Rails’ Concerns (which were added in Rails 4.0) for a slightly different approach.
- ActiveResource was removed and extracted into its own gem:
gem 'activeresource' # https://github.com/rails/activeresource
- Association names must be a Symbol
# before:
has_one "report_#{report}", class_name: 'MyReport'
# after:
has_one :"report_#{report}", class_name: 'MyReport'
- Add
multiline: true
tovalidates_format_of
when we use regular expression
Due to frequent misuse of ^ and $, you need to pass the multiline: true option in case you use any of these two anchors in the provided regular expression. In most cases, you should be using \A and \z.
# before:
validates_format_of :recipient_email, with: /^[^@][\w.-]+@[\w.-]+[.][a-z]{2,4}$/i
# after:
validates_format_of :recipient_email, with: /^[^@][\w.-]+@[\w.-]+[.][a-z]{2,4}$/i, multiline: true
conditions
attribute is replaced by lambda function with where clause
# before
belongs_to :valid_coupon, :conditions => 'discounts > #{payments_count}'
# after:
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }
- Active model serializers changes
The option include_root_in_json
controls the top-level behavior of as_json
. If true, as_json
will emit a single root node named after the object’s type.
In Rails 3, it is set to true
,
In Rails 4, it is set to false
by default. To enable it add following in config file.
ActiveRecord::Base.include_root_in_json = true
More details
# before:
user.as_json
=> { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
"created_at": "2006/08/01", "awesome": true} }
# after:
user.as_json
=> { "id" => 1, "name" => "Konata Izumi", "age" => 16,
"created_at" => "2006/08/01", "awesome" => true}
- ActiveRecord custom attributes (attributes not stored in the database) no longer allowed. More details
# before: Previously we would just silently write the attribute.
attr_accessible :months
object.months = "Test"
# After:
attr_accessible :months
object.months = "Test" # Raise exception - can't write unknown attribute
# Workaround
after_initialize do
self.months = nil unless @attributes.key?("months")
end
def months
@attributes["months"]
end
def months=(value)
@attributes["months"] = value
end
extend
association changes
1) With only extend
attribute
# before:
belongs_to :diagnosis, :extend => DiagnosisAssociationExtension
# After:
belongs_to :diagnosis, -> { extending DiagnosisAssociationExtension }
2) With extend
and conditions
attributes
# before:
belongs_to :clinic, primary_key: :person_id, foreign_key: :person_id,
conditions: conditions_proc, extend: MultiAssociation::OnTestId
# After:
belongs_to :clinic, -> { extending(MultiAssociation::OnTestId).where(conditions_proc) }, primary_key: :person_id, foreign_key: :person_id
b. Controllers
- ActionController Sweeper was extracted into the
rails-observers
gem, you can regain usage by adding the gem to your Gemfile:
gem 'rails-observers' # https://github.com/rails/rails-observers
- Action caching was extracted into its own gem, so if you’re using this feature through either:
caches_page :public
or:
caches_action :index, :show
You will need to add the gem:
gem 'actionpack-action_caching' # https://github.com/rails/actionpack-action_caching
gem 'actionpack-page_caching'
b. Active Support
BufferedLogger
is deprecated. Use ActiveSupport::Logger
, or the logger from Ruby standard library.
6. Tests
From Ruby 1.9.x onwards, you have to include the test-unit
gem
in your Gemfile as it was removed from the standard lib. As an alternative,
migrate to Minitest
, RSpec
or your favorite test framework.
7. Miscellaneous
- Routes now require you to specify the request method, so you no longer can rely on the catch-all default.
# change:
match '/home' => 'home#index'
# to:
match '/home' => 'home#index', via: :get
# or:
get '/home' => 'home#index'
- Rails 4.0 dropped support for plugins, so you’ll need to replace them with gems,
either by searching for the project on RubyGems /Github ,
or by moving the plugin to your
lib
directory and require it from somewhere within your Rails app.
8. Next steps
If you successfully followed all of these steps, by now you should be running Rails 4.0!
To fine-tune your app, check out FastRuby.io , and feel free to tell us how your upgrade went.
If you’re not on Rails 4.0 yet, we can help! Download our free eBook: The Complete Guide to Upgrade Rails .