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 onconfig/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:
- ActiveResource (Here’s a blogpost explaining the change.)
- ActiveRecord Observers and ActionController Sweepers
- ActiveRecord::SessionStore
- ActiveModel Mass Assignment Sanitizer
- Action Caching
- Page Caching
- Hash-based and dynamic finders
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 usingwhere(...).
find_last_by_...
can be rewritten usingwhere(...).last
.scoped_by_...
can be rewritten usingwhere(...)
.find_or_initialize_by_...
can be rewritten usingwhere(...).first_or_initialize
.find_or_create_by_...
can be rewritten usingfind_or_create_by(...)
orwhere(...).first_or_create
.find_or_create_by_...!
can be rewritten usingfind_or_create_by!(...)
orwhere(...).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:
I’m a developer, maker, and entrepreneur from Buenos Aires, Argentina. Mostly working with Ruby and Javascript. I love to create and work on great products that solve real world needs, and believe every problem deserves a thoughtful solution.