Caching in Rails

The information in this card is only relevant for Rails 2.3-era apps.


This note gives a quick introduction into caching methods (page caching, action caching and fragment caching) in rails and describes some specific problems and solutions.

The descriptions below are valid for Rails 2 and 3. Recently, caching with timestamp- or content-based keys has become more popular which saves you the pain of invalidating stale caches.

How to enable/disable caching

To enable or disable caching in rails you ca...

Upgrade from Ruby 1.8.7 to Ruby 1.9.2 on Ubuntu

Note that you cannot currently use Ruby 1.9.2 with Rails 2 applications that use RSpec, so don't upgrade if that is your setup. The rspec-rails gem has a fatal bug that was only fixed for rspec-rails-2.x, which only supports Rails 3. There is no fix for the rspec-rails-1.3.x series of the gem which supports Rails 2.

Anyway, here are upgrade instructions if you only work with Rails 3 or don't use RSpec. You will lose all your gems in the process, but you can get them back easily if you h...

Prevent an ActiveRecord attribute from being changed after creation

Sometimes you can make your life easier by not allowing a record attribute to be changed after the record was created. An example for this is when your model represents a child node in a composition and has logic that is hard to support for cases when the container changes.

Here is an example for a container Region composed of many children of type Holiday. After saving a Holiday it caches the current number of holidays in its region:

class Region < ActiveRecord::Base

...

Upgrading Cucumber and Capybara to the latest versions available for Rails 2

Specify these gem versions in your Gemfile:

gem 'cucumber', '~> 1.3.0'
gem 'cucumber-rails', '= 0.3.2' # max version for Rails 2
gem 'capybara', '< 2' # capybara 2+ requires Rails 3
gem 'mime-types', '< 2' # dependeny of capybara
gem 'nokogiri', '< 1.6' # dependency of capybara
gem 'rubyzip', '< 1' # dependency of selenium-webdriver, rubyzip 1+ requires Ruby 1.9
gem 'cucumber_factory'
gem 'database_cleaner', '< 1'
gem 'cucumber_spinner', '~> 0.2.5'
gem 'launchy', '~> 2.1.2'

With these versions set, `...

Onload callback for dynamically loaded images

Sometimes you need to dynamically load an image and do something as soon as its loaded (when for example its size is already available).

With jQuery, this seems to work across browsers:

$('<img>')
  .attr('src', '')
  .load(function() {
    alert('fully loaded!');
  })
  .attr('src', '/the/real/image/url');

Sanitize user-generated filenames and only send files inside a given directory

If in your application your users pass along params that result in filenames, like invoices/generated?number=123. This could be your (very careless) controller method:

def generated
  send_file File.join(Rails.root, 'shared', 'invoices', params[:number])
end

This allows your users not only to access those files but also any files your application can read, like this:

invoices/generated?number=../../../../../etc/passwd
# => send_file '/etc/passwd'

You do not want this. In most cases you should prefer a show met...

CSS3 Pie: Element not properly redrawn

Pie sometimes does not properly redraw elements upon changes. This often happens when the change comes from somewhere further up the DOM.

Consider something like:

<ul>
  <li class="active"><div class="content">Active element</div></li>
  <li class="inactive"><div class="content">Inactive element</div></li>
</ul>

with CSS
li .content {
-webkit-box-shadow: #666 0px 2px 3px;
-moz-box-shadow: #666 0px 2px 3px;
box-shadow: #666 0px 2px 3px;
behavior: url(/PIE.htc);

  back...

RSpec matcher to check if an ActiveRecord exists in the database

The attached RSpec matcher exist_in_database checks if a given record still exists in the database and has not been destroyed:

describe Ticket do
  describe '.purge_expired' do
    fresh_ticket = Ticket.create(:expiry => Date.tomorrow)
    expired_ticket = Ticket.create(:expiry => Date.yesterday)
    Ticket.purge_expired
    fresh_ticket.should exist_in_database
    expired_ticket.should_not exist_in_database
  end
end

Note that there is also [ActiveRecord::Base#destroyed?](http://apidock.com/rails/A...

Mocks and stubs in Test::Unit when you are used to RSpec

We are maintaining some vintage projects with tests written in Test::Unit instead of RSpec. Mocks and stubs are not features of Test::Unit, but you can use the Mocha gem to add those facilities.

The following is a quick crash course to using mocks and stubs in Mocha, written for RSpec users:

|---------------------------------------------------------|
| RSpec | Mocha |
|---------------------------------------------------------|
| obj = double() | obj = mock() |
| obj.stub(:method => 'value') | `obj.stubs...

Alternative to url_for's deprecated :overwrite_params option

If you have the following deprecation warning after upgrading to rails >= 2.3.10

DEPRECATION WARNING: The :overwrite_params option is deprecated. Specify all the necessary parameters instead.

that is for example caused by

url_for( :overwrite_params => { :order => 'name', :dir => 'asc' } )

you can fix this by using params.merge {:my_param_to_overwrite => 'foo' }.
To fix the example above the code could look like:

url_for( params.merge { :order => 'name...

Output the descriptions of RSpec examples while they are running

In order to

  • track down warnings and to see failing specs immediately
  • or to get an overview of the core functionalities,

you can use RSpec's "nested" format. It looks like this:

Tool
  validations
    should require model to be set
    should require place_id to be set
  #identifier
    should include the model and tag if the tool has a tag
    should return the model if the tool has no tag
  .search
    should find tools by model and maker
    should find tools by serial number

Call RSpec like...

Delete all MySQL records while keeping the database schema

You will occasionally need to clean out your database while keeping the schema intact, e.g. when someone inserted data in a migration or when you had to kill -9 a frozen test process.

Old Capybara versions already have the Database Cleaner gem as dependency. Otherwise add database_cleaner to your *Gemfile`. This lets you say this from the Rails console:

DatabaseCleaner.strategy = :truncation
DatabaseCleaner.cl...

Synchronize a Selenium-controlled browser with Capybara

When you click a link or a press a button on a Selenium-controlled browser, the call will return control to your test before the next page is loaded. This can lead to concurrency issues when a Cucumber step involves a Selenium action and a Ruby call which both change the same resources.

Take the following step which signs in a user through the browser UI and then sets a flag on the user that was just signed in:

Given /^the user "([^"]*)" signed in (\d) days ago$/ do |name, days|
  visit new_session_path
  fill_in 'Username', :w...

Unexpected behavior when changing both an association and its foreign key attribute in ActiveRecord

When you set both a record's association and that association's foreign key attribute, Rails does not realize you are talking about the same thing. The association change will win in the next save, even if the foreign key attribute was changed after the association.

As an example, assume you have these two models:

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  validates_presence_of :group_id
  belongs_to :group
end

We will now load a User and change both its `g...

Why two Ruby Time objects are not equal, although they appear to be

So you are comparing two Time objects in an RSpec example, and they are not equal, although they look equal:

expected: Tue May 01 21:59:59 UTC 2007,
     got: Tue May 01 21:59:59 UTC 2007 (using ==)

The reason for this is that Time actually tracks fractions of a second, although #to_s doesn't say so and even though you probably only care about seconds. This means that two consecutive calls of Time.now probably return two inequal values.

Consider freezing time in your tests so it is not dependent on the speed of the executi...

Check that an element is hidden via CSS with Spreewald

If you have content inside a page that is hidden by CSS, the following will work with Selenium, but not when using the Rack::Test driver. The Selenium driver correctly only considers text that is actually visible to a user.

Then I should not see "foobear"

This is because the Rack::Test driver does not know if an element is visible, and only looks at the DOM.

Spreewald offers steps to check that an element is hidden by CSS:

Then "foo" should be hidden

You can also check that an el...