Advertisement

Digging Into Rails 4

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

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 as DOMContentLoaded.
  • 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:

  1. removing turbolinks from your Gemfile, and
  2. removing the //= require turbolinks line from application.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 or close 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 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!.

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!

Advertisement