Migrating RSpec to Mocha

I have been using the builtin RSpec mocking framework for a couple of years and haven’t had any issues with it. The one feature I have been missing though is the any_instance method of the Mocha framework. What this method lets you do is mock a method on all instances of a given class. This is useful is you can’t get to the instance that you want to mock from your tests. It might be user object of the logged in user in a controller test for example. I also needed this feature when testing route generation since our routes depend on the subdomain of the host and the routing assertions in Rails will create a request object on the fly that I can’t get to.

Configuring RSpec to use Mocha is a oneliner. Since the Mocha DSL differs slightly from the RSpec DSL though the switch got a little more involved than that. Below are some notes that outline the details. Most noteworthy is that Mocha doesn’t support dynamic return blocks. This means you can’t mock/stub a method to execute a piece of code that takes the arguments of the method. What you need to do instead is map every expected set of argument values to a single return value. I suppose this works for most cases. James Mead intentionally designed the Mocha API with this limitation for what seems to be ideological reasons. His explanation is that being able to mock methods with code blocks “encourages the use of complex logic for computing return values which is not sensible mocking practice”. Maybe it is, maybe it isn’t. What I do know is that Mocha is an example of a really beautiful Ruby DSL. Don’t you agree?

PostgreSQL Unreliable Default Sort Order and Random Rails Test Failures

We have been struggling with intermittent Rails test failures for quite some time. There were times when we blamed our search engine Ferret, sometimes we blamed database interference between tests, and sometimes higher powers. Lately we have realized that all this time the ghost haunting us might have been the unreliable PostgreSQL default sort ordering. Here is what the docs have to say:

“If sorting is not chosen, the rows will be returned in an unspecified order. The actual order in that case will depend on the scan and join plan types and the order on disk, but it must not be relied on.”

Suppose you have a test that asserts that when requesting a certain action, then Article.first or @user.articles.first is assigned in the controller. This test may work five times, or a hundred times, or maybe even a thousand times before it fails. Maybe it so happens that your test fails when you run your full test suite and then succeeds when you run the test separately. Needless to say this can become frustrating very fast.

There is some inconsistency in ActiveRecord when it comes to first and last. ActiveRecord::Base#first will do a “SELECT * FROM “pressreleases” LIMIT 1″ (unordered and thus unreliable) but ActiveRecord::Base#last issues an “SELECT * FROM “pressreleases” ORDER BY pressreleases.id DESC LIMIT 1″ (ordered and reliable).

Note that the danger with unreliable PostgreSQL default sort ordering also applies when you are ordering by something that isn’t guaranteed to be unique, such as a publish date.

The solution we came up with was to issue a double sort order “published_at DESC, id DESC”. Unfortunately the drama escalated since we had forgotten to create a composite index on published_at and id leading to the database locking up and average response time going from 25 ms per transaction to 100 ms. The importance of composite indexes for ordering on multiple columns is illustrated by this example (note that the ordering of columns needs to match for the index to apply):

Rails 2.3.2 Bug - Sending Multipart Email Breaks When Current Directory is not RAILS_ROOT

We had the debugging session of the year here at Newsdesk the other day and we thought it would be prudent to warn other people about it. It turns out that ActionMailer in Rails 2.3.2 fails in sending multipart emails when the current working directory is not Rails.root. This happened to us when invoking the mailer from a cronjob.

The bug has been reported and solved with two similar patches. One patch is by Tom Lea and it has been committed and is available in the latest Rails 2 version (Rails 2.3.5). The issue stems from ActionMailer::Base.template_root not being a string (as you may naively believe when you print it out) but rather a subclass of ActionView::Template::Path. The Path class will return the relative path of the template when to_s is invoked but the absolute path when to_str is invoked. Others have tried to figure out how to_s and to_str are supposed to be used - two small and confusingly similar Ruby methods.

It turns out that when you interpolate the Path object into a string with “#{path}/#{filename}” (Rails before the patch), then to_s is invoked. When you use File.join(path, filename) then to_str is used (Rails after the patch).

With to_str and the absolute path in place you no longer have to stand in the RAILS_ROOT for ActionMailer to work. Cause for celebration…

Enabling/Disabling the Rails Cache

We needed a way to globally enable/disable the Rails cache so we came up with this patch:

By default ActionController will use the Rails.cache (RAILS_CACHE) object configured by config.cache_store for its fragment caching (action caching is an around filter that utilizes the fragment cache). However, it is possible to configure a different cache store via ActionController::Base.cache_store. If you do that you’ll end up with two different cache stores each with its own independent enabled attribute. For the sake of simplicity I try to make sure we stick to a single cache store object in our application, namely MemCacheStore.

Announcing Cachable Model - A New Plugin For Caching Your Rails Models

When you scale a Rails website the database layer tends to become the bottleneck sooner or later as it’s more difficult to scale horizontally with hardware than the Ruby/web server layer. At MyNewsdesk we have started looking at database caching in the model layer to offload our PostgreSQL database. Unfortunately caching does no longer seem to be in vogue in the Rails community and plugins related to model caching such as Cache Money, Cached Model, and Cache Fu are all dated and do not use the Rails.cache object available for in Rails as of version 2.1. Caching is complicated by the fact that our queries do complex joins and therefore have intricate dependencies which makes flushing the cache difficult since we require that what we display on the site is always in sync with the database. As a first stab we decided to take the Cached Model approach of caching primary key single record lookups and reserve more aggressive caching for a few models that change very rarely. We’ve put together our work in a new plugin called Cachable Model and we hope you find it useful.

Rails Gotcha: Mocking/Stubbing on an Associated ActiveRecord Object

In RSpec it is still not possible to have mocks or stubs on all instances of a class, although this has been suggested and it is a feature of the Mocha framework. Stubbing or mocking an object returned by an ActiveRecord belongs_to association (i.e. article.author) won’t do what you expect. Instead of mocking the associated object you end up mocking the AssociationProxy object that wraps the associated object. To work around this you can use the AssociationProxy#proxy_target method, something like this:

So when we think we are working directly with an ActiveRecord model, we are in fact working with a proxy object, and this can be confusing:

As far as I can tell this behaviour won’t change with Rails 3 since its AssociationProxy class works the same way.

Rails Tip: Safe Usage of URI.parse

As documented already by Doug URI.parse will thrown an exception if your URL has a trailing space. It also throws an exception on invalid URLs in general. To avoid having URI.parse bomb on your pages you can use a construct like this:

Keep Your I18n Translation Files Tidy

I added the Rake task translate:remove_obsolete_keys that lets you remove all obsolete translations from your YAML files. How is a translation rendered obsolete? Suppose you are translating from english to a number of other languages. Over time you may end up removing a number of I18n keys from the english translation file as your application evolves. The Rake task lets you remove those keys for all locales so you don’t have to do that manually.

Configuring Exception Handling in RSpec and Controller Tests

The ActionController::Rescue module in Rails defines a number of exceptions that will trigger a 404 response, namely ActionController::RoutingError, ActionController::UnknownAction, and ActiveRecord::RecordNotFound. However if you trigger one of those exceptions in a controller test (Test::Unit or Rspec) then rescue and display of the 404 page will not happen and instead the exception will just be propagated. If you prefer checking the 404 response code in your tests over catching one of the exceptions then you can make use of the method ActionController::TestCase#rescue_action_in_public!. The method will set the request.remote_addr to something other than 0.0.0.0 and this will trigger real exception handling. In RSpec you can invoke the method in your spec_helper.rb like this:

One thing to note about this approach is that it means all exceptions will be caught. Sometimes it may be preferable to let the exceptions propagate so that you are able to assert in your tests which exception class should be raised. Even with exception handling activated though, you can still access the exception that was raised by invoking controller.send(:exception) in your tests.

acts_as_bitfield Plugin now Radio Button Compatible

I’ve patched the acts_as_bitfield plugin to recognize “true” as true so that now instead of having to write:

<%= f.radio_button(:email_activated, true, :checked => (”checked” if @item.email_activated?)) %> Yes

<%= f.radio_button(:email_activated, false, :checked => (”checked” unless @item.email_activated?)) %> No

you can omit the checked argument just like with ActiveRecord boolean attributes:

<%= f.radio_button(:email_activated, true) %> Yes

<%= f.radio_button(:email_activated, false) %> No