Rails 4 is rapidly approaching. In this article, let's take a look at some of the new features that it offers, as well as the changes that may affect your current applications.
Some Bookkeeping
Cache digests are Rails 4′s solution for tracking the changes of aggressively cached templates.
There are several configuration and structural changes that comes with Rails 4.
Ruby >= 1.9.3
Rails 4 will only support Ruby 1.9.3+. Get ready for an upgrade if haven't yet done so.
Threadsafe by Default
Rails 4 will be thread-safe by default, removing overhead and improving performance on threaded servers, like thin and puma. You need to ensure that your application (and its dependencies) are thread-safe, which typically means avoiding global state (e.g. class or global variables).
Aaron Patterson wrote and spoke about this subject. Definitely check those out!
No More vendor/plugins
Rails 3 embraced the idea of using gems to add custom functionality to Rails, and deprecated the use of plugins. Rails 4 completes this transition by removing the vendor/plugins
directory altogether.
New Testing Directories
The default test directory naming scheme is more clear than in Rails 3.
The following directories will now be generated: test/models
, test/helpers
, test/controllers
, test/mailers
, and test/integration
.
Executables
The script
directory has been removed in favor of a new bin
directory. This is where your app's executables will live, and running rake rails:update:bin
will put bundle
, rake
, and rails
binstubs into your app's bin
directory.
This change can be useful in development, especially on a machine with multiple Ruby versions and gems. You can use bin/rails
instead of bundle exec rails
to ensure you run your executables in the correct environment.
Strong Parameters
Rails 4 tackles the mass assignment problem with the new Strong Parameters gem. A Rails 3 application might have a create
action similar to the following example:
class UsersController < ApplicationController def create @user = User.create(params[:user]) # ... check validity, redirect, etc. end end
You can protect against unexpected input with declarations in the model:
class User < ActiveRecord::Base # Only allow the following attributes to be mass-assigned attr_accessible :name, :email end
Using Rails 4's Strong Parameters gem moves user input into the controller:
class UsersController < ApplicationController def create @user = User.create(user_params) # ... check validity, redirect, etc. end def user_params params.require(:user).permit(:name, :email) end end
As you can see, the params
hash in your controller is not a normal hash. It's actually an instance of ActionController::Parameters
, which exposes the require
and permit
methods.
The require
method ensures that the specified key is available in the params
hash, and raises an ActionController::ParameterMissing
exception if the key doesn't exist.
The
permit
method protects you from unexpected mass assignment.
The call User.create(params[:user])
raises an ActiveModel::ForbiddenAttributesError
exception, but using User.create(params.require(:user).permit(:name, :email))
makes it work without complaint.
The Rails 3 mass-assignment functionality is not only disabled in Rails 4, but has been extracted to a gem, in case you require that functionality.
Turbolinks
Rails 4 will be thread safe by default, removing overhead and improving performance.
A controversial new feature in Rails 4 is Turbolinks, a JavaScript plugin designed to make app navigation faster in the browser.
In browsers with pushState
support, clicking a link causes the Turbolinks plugin to kick in. It makes an Ajax request, updates the URL with pushState
(so your back button works) and uses JavaScript to update the <title>
and <body>
in the DOM. The speed gains come from not having to download and reparse JavaScript and CSS assets.
Turbolinks gracefully degrade for browsers which do not support pushState
. In these situations, the page's links behave as normal—causing a full page refresh.
Events and Cache
It's common in applications to wait for a page to completely load before executing any JavaScript. For example:
$(document).ready(/* some function to run */) { // or event just $(/* some function to run */) }
With Turbolinks, the page load events won't fire when users navigate from "page" to "page" because the DOM never actually reloads. The library, therefore, adds new events that you can listen for, in order to perform any subsequent initializations that your app might need:
-
page:fetch
- starting to fetch a page from the server -
page:change
- a page has been loaded -
page:load
- a page has been loaded from a server fetch -
page:restore
- a page has been loaded from a cache fetch
The page:change
event always fires when Turbolinks loads a page, followed by page:load
or page:restore
, depending on whether the load came from the server or the cache.
Potential Issues
Rails 4 is coming, and it brings a slew of changes to the framework.
Turbolinks have a few issues that you might need to address:
- Memory leaks: Turbolinks does not clear or reload your JavaScript when the page changes. You could potentially see the effects of memory leaks in your applications, especially if you use a lot of JavaScript.
-
Event Bindings: You have to take older browsers into consideration. Make sure you listen for
page:*
events, as well asDOMContentLoaded
. - Client-side frameworks: Turbolinks may not play nicely with other client-side frameworks like Backbone, Angular, Knockout, Ember, etc.
Opting Out
You may opt out of Turbolinks by:
- removing
turbolinks
from your Gemfile, and - removing the
//= require turbolinks
line fromapplication.js
Caching
Rails 4 brings an overhauled caching strategy. First, action and page caching, as you may know it from previous versions of Rails, have been removed and extracted to gems: action and page, respectively.
Russian Dolls
The new kid on the block is Russian doll caching, or nested fragment caching. The easiest way to understand this system is to look at some code. Suppose that you have a project management application. You may have the following models:
class Milestone < ActiveRecord::Base has_many :todos end class Todo < ActiveRecord::Base belongs_to :milestone, :touch => true end
The :touch
option is required for this caching strategy to work properly. If a todo is added to a milestone, we need to break cache on the milestone to avoid serving stale views.
We now have finely-grained caches in our views. Consider this file as an example (app/views/milestones/show.html.erb
):
<% cache @milestone do %> <h1><%= @milestone.name %></h1> <div class="description"><%= @milestone.description %></div> <ul class="todos"> <%= render @milestone.todos %> </ul> <% end %>
And in app/views/todos/_todo.html.erb
:
<% cache todo do %> <li class="todo"> <%= todo.description %> <span class="status"><%= todo.status %></span> </li> <% end %>
Now, suppose that you have a milestone with ten todos. Editing only one todo causes the milestone's cache to break, but when generating the HTML, all but one of the todo partials can be fetched from the cache, thus improving render times.
PATCH is now the new HTTP verb for updating resources.
You're trading time for space, as this generates a lot of cruft in your cache. But, as DHH points out, cache stores like Memcached just chuck out old data to make space for new data. So this isn't an issue in most cases.
Cache Digests
Cache digests are Rails 4's solution for tracking the changes of aggressively cached templates. Rails 4 tracks templates and their dependencies, and it suffixes fragment cache keys with the MD5 digest of the template (and its dependencies). When you edit one of your templates, its cache key recieves the update, and you won't have to manually version your templates.
For more information (and for use in Rails 3), check out the README for the cache digests gem.
Streaming, via ActionController::Live
The new ActionController::Live
module provides the ability to stream data to clients. Simply include
the module into a controller to enable your app to send arbitrary streamed data. You'll have to use a threaded server, like thin and puma, in order to stream data; actions from streaming controllers run in a separate thread.
Here's an example from the Rails 4 documentation:
class MyController < ActionController::Base include ActionController::Live def stream response.headers['Content-Type'] = 'text/event-stream' 100.times { response.stream.write "hello world\n" sleep 1 } response.stream.close end end
As the docs note, there are three things to keep in mind:
- You must write any headers before you call
write
orclose
on the response stream. - You have to call
close
on the response stream when you're finished writing data. - Ensure that your actions are thread-safe, as they will run in a separate thread.
Niceties and Other Things
We've talked about the "headline" features in Rails 4. But this release is a big one, and includes a number of smaller changes to be aware of.
PATCH
As described in the Rails blog, PATCH is now the HTTP verb for updating resources.
This change will typically be transparent to developers, as PUT requests will still route to the
update
action for RESTful-style routes.
But it is a change that you should be aware of; PUT routing may change in the future.
Custom Flash Types
This small feature may help clean up some code. You can register your own flash types to use in redirect_to
calls and in templates. For example:
# app/controllers/application_controller.rb class ApplicationController add_flash_types :error, :catastrophe end # app/controllers/things_controller.rb class ThingsController < ApplicationController def create # ... create a thing rescue Error => e redirect_to some_path, :error => e.message rescue Catastrophe => e redirect_to another_path, :catastrophe => e.message end end # app/views/layouts/application.html.erb <div class="error"><%= error %></div> <div class="catastrophe"><%= catastrophe %></div>
Deprecated Finders
Rails 4 deprecates the old-style finder option hashes, as well as all dynamic finder methods (with the exception of find_by_...
and find_by_...
). Instead, you'll use where
:
-
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!
.
The deprecated finders gem will be included as a dependency in 4.0. and removed in 4.1. The gem, however, will be around and maintained until 5.0.
Routing Concerns
Routing Concerns is an attempt to DRY up your config/routes.rb
. The basic idea is to define common sub-resources (like comments) as concerns and include them in other resources/routes. Here's the obvious example:
concern :commentable do resources :comments end concern :remarkable do resources :remarks end resources :posts, :concerns => :commentable resources :articles, :concerns => [:commentable, :remarkable] # can include several
The above is equivalent to the following Rails 3 code:
resources :posts do resources :comments end resources :articles do resources :comments resources :remarks end
Personally, I'm not sure this adds much value; perhaps it makes sense for large applications with hundreds of routes.
Renamed Callbacks
Action callbacks in controllers have been renamed from *_filter
to *_action
. For example:
class UsersController < ApplicationController before_action :set_user, :except => [:index, :new, :create} before_action :require_the_president, :only => [:fire_the_missiles] private def set_user @user = somehow_find_and_set_the_user(params[:id]) end def require_the_president @user.is_the_president? end end
The old *_filter
callbacks still work and are not deprecated; so, you can still use them if you wish. DHH's reason for the change was:
"To avoid the misconception that these callbacks are only suited for transforming or halting the response. With the new style, it's more inviting to use them as they were intended, such as setting shared ivars for views."
Wrapping Up
Rails 4 is coming, bringing with it a slew of changes. I hope that this article has given you a sense of what to expect, and perhaps a launching point into investigating what this new version has to offer.
If you really want to wade into the deep end, check out our Tuts+ Premium course on Rails 4!
Comments