The problem of caching model instances on a constant in Rails
I am using Single-Table Inheritance (STI) on one of my models for a Rails application and I am having trouble storing model objects in a constant. I split the issue into a sample project and submitted it to GitHub: http://github.com/fcoury/rails-sti-caching
What I'm trying to do is load an instance of the model (in this case the Music model, which inherits from the Media model via STI) in an initializer (in the Rails' directory /config/initializers/
) and store it on a constant:
MUSIC_CACHE = Hash.new Music.all.each { |m| MUSIC_CACHE[m.id] = m }
And I have a control sample that does the following:
class MusicsController < ApplicationController
def index
require 'pp'
pp MUSIC_CACHE
@debug = []
MUSIC_CACHE.each_pair do |k, v|
music = Music.find(k)
d "Object for Music.find(#{k}) => class: #{music.class} - class obj_id: #{music.class.object_id} - #{music.inspect}"
d "Object for MUSIC_CACHE[#{k}] => class: #{v.class} - class obj_id: #{v.class.object_id} - #{v.inspect}"
begin
d " - Music.is_a?(Media) => #{v.is_a?(Media)}"
d " - Try to call name => #{v.name}"
rescue
d "*** Error raised:\n#{$!}"
end
end
@musics = Music.all
end
def d(s)
puts s
@debug << s
end
end
And let's see how to do it:
<h1 id="music">Music</h1>
<ul>
<% for m in @musics %>
<li><%= m.name %> - <%= m.file %></li>
<% end %>
</ul>
<pre><%=h @debug.join("\n") %></pre>
The first time you run this code, the output of the tag is <pre>
as follows:
Object for Music.find(2) => class: Music - class obj_id: 13067420 - #<Music id: 2, name: "5th Symphony", file: "5s.mp3", type: "Music", created_at: "2009-05-06 16:31:41", updated_at: "2009-05-06 16:31:41">
Object for MUSIC_CACHE[2] => class: Music - class obj_id: 13067420 - #<Music id: 2, name: "5th Symphony", file: "5s.mp3", type: "Music", created_at: "2009-05-06 16:31:41", updated_at: "2009-05-06 16:31:41">
- Music.is_a?(Media) => true
- Try to call name => 5th Symphony
However, if I just reload the page, this is what happens:
Object for Music.find(2) => class: Music - class obj_id: 18452280 - #<Music id: 2, name: "5th Symphony", file: "5s.mp3", type: "Music", created_at: "2009-05-06 16:31:41", updated_at: "2009-05-06 16:31:41">
Object for MUSIC_CACHE[2] => class: Music - class obj_id: 13067420 - #<Music id: 2, name: "5th Symphony", file: "5s.mp3", type: "Music", created_at: "2009-05-06 16:31:41", updated_at: "2009-05-06 16:31:41">
- Music.is_a?(Media) => false
*** Error raised:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.include?
Does anyone know the rationale behind this error?
a source to share
My first idea would be for Rails to free (invalidate) all model objects after serving the request. Thus, class: Music - class obj_id: 13067420
it becomes unusable for the second request. I would suggest looking at the ActiveRecord source code and figuring out who is invalidating the model objects.
Also, this rails model caching tutorial might be helpful: http://railscasts.com/episodes/115-caching-in-rails-2-1
a source to share
Basically, you cannot do this.
In a development environment, your classes are reloaded with every request. This means that they are completely destroyed and recreated. The original class object is gone, the new one takes its place.
If you persist the object across requests, in the second request the object will still inherit from the original class, the one that was removed. The constant pointing to this class now points to a new class object, which may or may not be identical to the previous one, depending on whether you changed the class definition or the plugins that affect it, but it will still be a different class object in memory and the old object won't know that it should inherit from this new class object.
I am assuming that if you run your application in production it will work.
a source to share