May 12, 2010
Rails developers know that in development mode classes are loaded on demand. In production mode all the classes are loaded as part of bootstrapping the system. Also in development mode classes are reloaded every single time page is refreshed.
In order to reload the class, Rails first has to unload
. That unloading is
done something like this.
# unload User class
Objet.send(:remove_const, :User)
However a class might have other constants and they need to be unloaded too.
Before you unload those constants you need to know all the constants that are
defined in the class that is being loaded. Long story short rails keep track of
every single constant that is loaded when it loads User
or UserController
.
Sometimes dependency mechanism by rails lets a few things fall through the crack. Try following case.
require 'open-uri'
class UsersController < ApplicationController
def index
open("http://www.ruby-lang.org/") {|f| }
render :text => 'hello'
end
end
Start the server in development mode and visit http://localhost:3000/users
.
First time every thing will come up fine. Now refresh the page. This time you
should get an exception uninitialized constant OpenURI
.
So what's going on.
After the page is served the very first time then at the end of response rails
will unload all the constants that were autoloaded including UsersController
.
However while unloading UsersContorller
rails will also unload OpenURI
.
When the page is refreshed then UsersController
will be loaded and
require 'open-uri'
will be called. However that require will return false
.
Try the following test case in irb.
step 1
irb(main):002:0> require 'ostruct'
=> true
step 2
irb(main):005:0* Object.send(:remove_const, :OpenStruct)
=> OpenStruct
step 3 : ensure that OpenStruct is truly removed
irb(main):006:0> Object.send(:remove_const, :OpenStruct)
NameError: constant Object::OpenStruct not defined
from (irb):6:in `remove_const'
from (irb):6:in `send'
from (irb):6
step 4
irb(main):007:0> require 'ostruct'
=> false
step 5
irb(main):009:0> OpenStruct.new
NameError: uninitialized constant OpenStruct
from (irb):9
Notice that in the above case in step 4 require returns false
. 'require'
checks against $LOADED_FEATURES
. When OpenStruct
was removed then it was not
removed from $LOADED_FEATURES
and hence ruby thought ostruct
is already
loaded.
How to get around to this issue.
require
loads only once. However load
loads every single time. In stead of
'require', 'load' could be used in this case.
irb(main):001:0> load 'ostruct.rb'
=> true
irb(main):002:0> OpenStruct.new
=> #<OpenStruct>
In our rails application refresh of the page is failing. To get around to that
issue use require_dependency
instead of require
. require_dependency
is a
rails thing. Under the hood rails does the same trick we did in the previous
step. Rails calls kernel.load
to load the constants that would fail if require
were used.
If this blog was helpful, check out our full blog archive.