Manually requiring your application's models will lead to trouble

Updated . Posted . Visible to the public.

In a nutshell:

If you require your Rails models manually, pay attention to the path you use. Unless you have to, don't do it at all.

Background

Consider these classes:

# app/models/user.rb

class User < ActiveRecord::Base
  validate :magic

  def magic
    errors.add_to_base('failed') if bad_things?
  end
end

^
# app/models/foo.rb

require 'user'

class Foo
  # something happens here
end

Now, when your environment is booted, Rails will automatically load your models, like User.

In our example, when Foo gets loaded, it will require the User class itself again. Since Ruby 1.8 identifies required files by the string you used (as you alread know), this will mean the User class is required a second time.

This, in turn, will lead to the effect that User#magic is called twice when a User record is validated.

Note that loading classes a second time will lead to all kinds of trouble. Stay away from that, Kids!

Fixing it

You have two options:

  1. Don't do it. Rails will load your models, so there is almost no reason to load it yourself.

  2. If you absolutely have to do it, use the path that Rails would use. In my case, the only correct answer would have been:

    require File.join(Rails.root, 'app/models/user')
    

It gets worse

The problem gets more strange when this issue "magically" appears or disappears.

If the Foo class from our example is not being auto-loaded, all will be fine for the most time, but you will encounter validation errors being added twice occasionally.

This happened to me when during tests: a spec was referring to Foo, which was not yet loaded, because it was in lib/foo.rb. When the spec was run, Rails loaded the class the first time it was referred to, causing user to be loaded, which Ruby believed it had not seen before. And that caused a completely different spec to fail at a later point of the spec run, because now User ran its manual validation method twice.

Consider the fun you'll have when running tests in parallel.

Arne Hartherz
Last edit
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2012-10-19 15:46)