One of Rails’ slightly gnarly areas is all the magic that goes into enabling the automatic reloading of source in development mode[1]. Reloading a class isn’t just as simple as reading the source again: that would just reopen the class. While this would allow you to add or change existing methods, it wouldn’t allow you to remove methods, change the class an object inherits from, stop including a module and things like that. In the particular context of Rails this would also cause validations, filters and callbacks to be added repeatedly. You also don’t want to reload absolutely everything. For example reloading standard ruby libraries would be pointless (and slow) as would be reloading Rails itself and (usually) plugins.
A related service that Rails’ dependencies system provide is autoloading of constants. Rails hooks const_missing
: when an unknown constant is found Rails will try and determine the name of the file containing it (according to Rails’ conventions) and search for it in the appropriate folders. After a request (or when you call reload!) Rails unsets the constant. This means that reading the corresponding file again will create a new class rather than reopening the old one. It also means that the next use of that constant will cause const_missing
to be hit again and load the class.
require messes with reloading
The long and short of this is that Rails needs to track what needs to be reloaded (i.e. which constants it should remove). When a file is loaded via Rails’ dependency system, all the constants are stashed away, in Dependencies.autoloaded_constants[2]. At the end of the request all of those constants are removed. But if you have bypassed the Rails dependency system then it won’t get that treatment. Here’s an example script/console session
1 2 3 4 5 6 7 8 9 |
|
The reload!
function does the reloading that Rails would do at the end of a request. Here everything is happening as normal: we’ve let Rails handle the loading and after the reload the Customer constant is removed, ensuring we then get a fresh copy of the Customer class.
Now lets try something different: explicitly require customer.rb:
1 2 3 4 5 6 7 8 9 |
|
Lo and behold: the Customer class isn’t being reloaded. Had you done this in a real app you would find that changes to the customer file weren’t being picked up until you restarted the server. Even more confusingly it would be fine until you loaded a file that did such a require but thereafter changes would have no effect, even on pages where previously it worked.
Fun with associations
A lot of problems happen when you have something hanging onto an old version of a class. One way that can happen in a Rails app is via associations. Suppose our Customer class has an orders association.
1 2 3 4 5 6 7 8 9 10 11 |
|
Everything is as we would expect it. Customer.reflections[:orders]
returns an AssociationReflection object which is something that describes an association. It holds data like what kind of association it is, any options that were supplied (eg :foreign_key
, :counter_cache
) and so on. In particular its klass attribute is the ActiveRecord::Base subclass for the association. Here we can see that that class is the same as Order which we would expect.
The association’s class has the methods you would expect: some methods to deal with the customer association that Order has and an instance method we added. So far so good. Lets reload the code:
1 2 3 4 5 |
|
Superficially things look fine, but if we dig a little deeper, everything has gone horribly wrong. The first clue is this:
1 2 3 4 |
|
This tells us that the Order class is no longer the same class as the class referenced by the association. Because Order was loaded via the Rails’ dependencies system it was reloaded when we did reload! but as we saw before Customer isn’t. This causes quite a few problems, for example
1 2 |
|
Oh noes! When you add a record to a collection Active Record checks that it is of the correct type, but the Customer class is trying to check that the object is an instance of the old Order class, which it isn’t. The fun thing about this sort of situation is that it will work fine the first time you view the page after restarting the server, but not the second or following times. Madness!
There’s more stuff too. If we repeat our earlier test to list the instance methods of the association’s class we get this:
1 2 3 4 |
|
They’ve all gone. This can be more than a little baffling, when a page works fine but reloading it causes methods you know exist to just disappear into thin air. The culprit here is the reset_subclasses
method in Active Record, which as its name implies, clears out classes. It only does this to autoloaded classes, which normally is fine because such classes are just thrown away and never used again, but we’re hanging onto this gutted class and trying to use it[3]. Even if this gutting of classes didn’t happen you’d still have a lot of confusion: instances of Order retrieved via the association would be the old class and so wouldn’t reflect any changes you had made to the source, but instances created directly would.
Just don’t do it
By now you’ve probably got the message that using require to load your models can cause some weird stuff to happen. Loading classes behind Rails’ back just gets things confused. There are two ways to stop this happening:
- Just don’t require stuff. If you lets Rails’ automagic loading do its work none of this will happen
- If you do need to require stuff explicity, use
require_dependency
. This means that Rails is kept in the loop
Of course require is fine for requiring gems, bits of standard libraries and so on, but using require to load bits of your own application should be viewed with suspicion. It only takes one require somewhere to mess things up, so be careful.
[1] Or to be quite precise, when config.cache_classes
is set to false. If it is set to true (for example in production mode) nothing in this article applies
[2] In Rails 2.2 and higher, Dependencies was moved into the ActiveSupport namespace. If you’re running that version mentally prepend ActiveSupport:: wherever you see Dependencies. There are a lot of other settings in there that control all of this, for example load_once_paths
and explicitly_unloadable_constants
allow you to control what is reloaded and what isn’t.
[3] As far as I can tell and according to this thread the exact reason this is necessary is rather lost in the mists of time, possibly an artefact of previous implementations of Rails’ dependencies.