Get Your App Ready for Rails 4

A new Rails version is about to be released and it’s coming with many changes, deprecations, and new features.

Lets take a look at what you need to do to get your app ready for Rails 4.

The Path to Upgrade

The easiest way to be ready for Rails 4 is having your app on Rails 3.2.
If you are not in 3.2, upgrading should be done by minor versions. That is from 3.0 to 3.1, and then from 3.1 to 3.2.

Deprecations

Some things are going to be completely removed on Rails 4. We should know about them and be prepared for the upgrade.

Ruby 1.9.3+ Only

Rails 4 is going to target only Ruby 1.9.3 and future versions. So make sure that your app runs on it.

If you’re using Ruby 1.9.x, the upgrade should be pretty straightforward.

If you’re still on Ruby 1.8.7, things could be a bit more work.

Nowadays most gems should be running on 1.9 or have a replacement.
Tools like rvm or rbenv help a lot by letting you run multiple versions of Ruby.

Using conditional statements in your Gemfile come in handy for having different gems on each environment until you get it all running on 1.9.

It should look something like this:

gem 'ruby18-only-gem', :platforms => :ruby_18
gem 'ruby19-only-gem', :platforms => :ruby_19

#More on this can be seen in the Bundler manpages:
#http://gembundler.com/man/gemfile.5.html#PLATFORMS-platforms-

A great resource for 1.8 vs 1.9 changes is the Ruby 1.9 walkthrough by Peter Cooper. Strongly recommended!

No More vendor/plugins

Rails 4 will be removing Rails::Plugins class. Therefore, any code in the vendor/plugins directory won’t be loaded.

Most apps were already relying on gems instead of vendored plugins. However, if you still have any code in vendor/plugins, you have 2 options:

  • Move it to a gem. Most plugins should already have a gem version. If not, you can reference it from your Gemfile via the :git or :path options.
  • Move it to lib/your_plugin and require it from an initializer on config/initializers.

Here’s the commit where this was deprecated.

Route Matching

On routes, the match method will no longer act as a catch-all option. You should now specify which HTTP verb to respond to with the option :via

#Rails 3.2
match "/users/:id" => "users#show"
#Rails 4.0
match "/users/:id" => "users#show", via: :get

#or specify multiple verbs
match "/users" => "users#index", via: [:get, :post]

Another option for better Rails 3.2 compatibility is to just specify your actions with explicit get, post, or any other HTTP verb. With this option, you still get your code running today and future proof it for the upgrade.

#Rails 3.2 and 4.0 compatible

get "/users/:id" => "users#show"

# multiple verbs

get "/users" => "users#index"
post "/users" => "users#index"

ActiveRecord Scopes Need A Callable object

In Rails 4, all ActiveRecord scopes must be defined with a callable object (like Proc or lambda):

#Rails 3.2
scope :recent, where(created_at: Time.now - 2.weeks)
#Rails 4
scope :recent, -> { where("created_at > ?", Time.now - 2.weeks).order("created_at desc") }
scope :active, -> { where(status: 'active') }

This helps avoid subtle bugs with Date or Time objects that get evaluated once, instead of being dynamically evaluated.

Removed Code

A lot of code was also removed from Rails 4 codebase. Don’t worry, though, most is still alive as separate gems. This helps upgraders with smooth transiitons.

Removed code:

A special note for the latter is worth mentioning:

Activerecord-deprecated-finders will be a default dependency on Rails 4.0 to provide the deprecated functionality. But it will removed on 4.1. As such, you should pay close attention to all warnings and start fixing them!

Rails guides provides a helpful explanation on changing most common cases on dynamic finders:

All dynamic methods except for findby… and findby…! are deprecated. Here’s how you can rewrite the code:

  • find_all_by_... can be rewritten using where(...).
  • find_last_by_... can be rewritten using where(...).last.
  • scoped_by_... can be rewritten using where(...).
  • find_or_initialize_by_... can be rewritten using where(...).first_or_initialize.
  • find_or_create_by_... can be rewritten using find_or_create_by(...) or where(...).first_or_create.
  • find_or_create_by_...! can be rewritten using find_or_create_by!(...) or where(...).first_or_create!.

All these gems will help having a smooth transition. I’d recommend that, for a full Rails 4 experience, just use them in development allowing the warning messages to help you get all your code up to the latest syntax.

New Features

Now it’s time for the fun part!

A lot of things were added to Rails 4. Good news is that most of them are also available today via gems for rails 3.2 apps. We can have a preview and at the same time get our code ready for the upgrade!

Let’s dive into it.

Strong Parameters (gem)

A lot of controversy was generated around the mass assignment protection. Since then, various different approaches were explored.

Rails 3.2 uses attr_accessible and attr_protected on the models.

Rails 4 takes a whole different point of view, making the controller responsible on what attributes are assigned to the model.

#Rails 3.2
class User < ActiveRecord::Base
  attr_protected :name, :email
end

class UsersController < ApplicationController
  def create
    @user = User.new params[:user]
    if @user.save
      redirect_to @user
    else
      render :new
    end
  end
end
#Rails 4.0

class User < ActiveRecord::Base
  include ActiveModel::ForbiddenAttributesProtection
end

class UsersController < ApplicationController
  def create
    @user = User.new params.require(:user).permit(:name, :email)
    if @user.save
      redirect_to @user
    else
      render :new
    end
  end
end

This could also be simplified to something like

#Rails 4

class UsersController < ApplicationController
  def create
    @user = User.new user_params
    #  ...
  end

  private
  def user_params
    params.require(:user).permit(:name, :email)
  end
end

An advantage of this approach is that it allows us to filter different parameters in a user facing action than in an admin area.
Also, different actions could permit different sets of allowed attributes. Having the controller in charge of what the model receives makes sense.

One more thing to have in mind when using this gem, you need to add this line on config/application.rb:

config.active_record.whitelist_attributes = false

The gem also provides a very clear README, worth reading. Luckily, there’s a Railscast for the more curious.

Cache Digests and Russian Doll Caching (gem)

As action and page caching are removed from Rails 4, there is introducing a new way to store caches. It’s via Key based expiration.
The main idea is to append the updated_at attribute to the cache key. When a record changes, it’s cache key changes and it will fetch a fresh version of it.

This approach generates a lot of cache garbage, so it works better with memcache-like stores where it removes oldest keys first when it’s running out of space.

Also, for views it generates a MD5 checksum of the file on the cache key. If anything in the file changes, the cache key changes and it fetches everything again.

#Rails 3.2
<% cache ['v1', @user] do %>
...
<% end %>

 

#Rails 4.0
<% cache @user do %>
...
<% end %>
#this will generate a key like
# views/users/1-201222172130/1a79a4d60de6718e8e5b326e338ae533

This works particullary well with nested views, as it’s aware of nested templates.

Here’s a great Railscast about this topic.

Declarative ETags (gem)

In Rails 4 you will be able to set controller-wide ETag suffixes. This helps a lot when an action depends on multiple records or a logged in user.

It’s a really easy thing to do to get better HTTP Caching.

A simple example:

class TodosController < ApplicationController
  etag { current_user.try :id }
  etag { @todo_list }

  before_fiter :find_todo_list

  def show
    @todo = @todo_list.todos.find(params[:id])
    fresh_when(@todo)
  end

  private
  def find_todo_list
    @todo_list = TodoList.find(params[:todo_list_id])
  end
end

In this example, the first etag block checks for a current user, ensuring no two logged users have the same cache ETag. The second one uses the TodoList object as part of the ETag, so a change to the todo list is a change to the ETag.

Turbolinks (gem)

Turbolinks is kind of like the brother of pjax. It uses pushState when available, triggering an XHR request and replacing the <body> content. This has 3 main benefits:

  • Doesn’t download CSS/JS twice.
  • Doesn’t recompile.
  • Doesn’t reevaluate.

Rails 4 comes with Turbolinks enabled by default. But this can lead to some problems if you’re using too much code in the domready event. TurboLinks declares 4 new events:

  • page:fetch starting to fetch the target page (only called if loading fresh, not from cache).
  • page:load fetched page is being retrieved fresh from the server.
  • page:restore fetched page is being retrieved from the 10-slot client-side cache.
  • page:change page has changed to the newly fetched version.

You’ll have to check your javascript and fix it where necessary.

To use it now, just install the gem and add //= require turbolinks to your main javascript file (usually application.js)

Here’s a great Railscast about this feature.

Concerns

Rails 4 introduces a new default pattern in order to avoid code repetition.

Routing Concerns (gem)

When there are multiple resources or methods shared between routes, there is also a high amount of code repetition. This feature gives us a new concern definition on routes, which helps improve the readability and makes code more DRY.

#Rails 3.2

resources :messages  do
  resources :comments
  post :trash, :restore, on: :member
end

resources :documents do
  resources :comments
  post :trash, :restore, on: :member
end

 

#Rails 4

concern :commentable do
  resources :comments
end

concern :trashable do
  post :trash, :restore, on: :member
end

resources :messages, :documents, concerns: [:commentable, :trashable]

This can be used in Rails 3.2 by simply adding the routing_concerns gem to your app.

Models and Controller Concerns

By default, Rails 4 adds two new folders to the default directory structure and load path: app/controllers/concerns and app/models/concerns.
The idea behind this is explained by DHH in this post.

Usually, this code would live in lib. These new directories make things cleaner and easier to find.

To do this now, you simply have to add this line to your config/application.rb

config.autoload_paths += Dir["#{config.root}/app/controllers/concerns", "#{config.root}/app/models/concerns"]

And move your model and controller modules to those directories.

Dalli as memcache Adapter (gem)

Dalli will replace memcache-client as the default gem for cacheing.
This brings several improvements for free:

  • It’s approximately 20% faster than the memcache-client gem.
  • Handles failover with recovery and adjustable timeouts.
  • It’s threadsafe.
  • Uses the newer memcached binary protocol.

To test this in your app today, you can do a couple of simple changes.

In your Gemfile:

gem 'dalli' 

In config/environments/production.rb (or whatever environment you use memcached):

config.cache_store = :dalli_store
# just be sure to change this back to :mem_cache_store when upgrading to Rails 4

One thing to note: Dalli depends on memcached 1.4+, so if you’re using an older version, be sure to update also!

Thread-safe by Default

In new Rails 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.

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?

You can enable threadsafe! in your apps right now to see how they behave. When upgrading to Rails 4 you will see a warning, and you can remove this line all along.

Summary

Well, we’ve covered a major set of changes on the upcoming Rails 4 release. With these changes, most apps should be easy to update. Anyway, Rails 4 is still in active development, so some other changes could be necessary in the future. I aim to keep this post updated in that case.

Further reading:

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://www.matthuggins.com Matt Huggins

    Thanks for compiling this! Looks like you forgot to include a reference to the :trashable concern in the Rails 4 example. :)

  • http://dreepi.com Esteban Pastorino

    Oops, missed that.
    Updated :)

  • http://evantravers.com Evan Travers

    Great article. Quick note… you mis-spelled attr_accessible as attr_accesible at one point, thought you should know. Thanks for a great summary!

  • Lonny Eachus

    Look, I really don’t want to be a Grammar Nazi, but there are basic rules of English that are broken several times in this article. One should not use the word “on” when what one really means is “from”, “for” or “in”. “On” is not a universal substitute for every preposition in the language.

    This is not a small thing. You would not say “I bought some food on my dog,” or “I put the pill on my mouth,”, or “I took the candy on the baby.” Those just don’t make sense. However, those are the kinds of sentences used in a few places in this article, except that of course the subject matter was Rails.

    Maybe get someone with good English skills to proofread before publication? Just a thought.
    ===

    Anyway, now that I have probably made some enemies: I would like to state that I really do appreciate the summary of differences between Rails 3.2 and Rails 4.

    Thanks.

  • Mario

    Hi, isn’t rails4 coming with `before_action` instead of `before_filter`? Since you used the filter version in one of your code blocks…

  • Praveen Srivastava

    i am enteresting to learn Ruby on Rails

  • Taimoor Changaiz

    Informative article. Explained me most of new things in rails 4.

    Thanks for sharing

  • Anders Lundström

    order(“created_at desc”)

    should be in rails4 afaik:
    order(created_at: :desc)