views:

586

answers:

2

I am using Single-Table Inheritance (STI) on one of my models for a Rails App and I am having problems storing model objects on a constant. I have isolated the problem into a sample project and submitted it to GitHub: http://github.com/fcoury/rails-sti-caching

What I am trying to do is loading a model instance (in this case a Music model, that inherits from the Media model via STI) on an initializer (in Rails' /config/initializers/ directory) and keep it on a constant:

MUSIC_CACHE = Hash.new
Music.all.each { |m| MUSIC_CACHE[m.id] = m }

And I have a sample controller 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 a view to go with 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 this code runs, the output on the <pre> tag is this:

  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, here's what gets outputted:

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?

+2  A: 

My first idea would be that Rails frees up (invalidates) all model objects after serving a request. So class: Music - class obj_id: 13067420 becomes unusable in the 2nd request. I'd suggest having a look at the ActiveRecord source code and finding out who invalidates model objects.

Also this rails model caching tutorial can be useful: http://railscasts.com/episodes/115-caching-in-rails-2-1

pts
pts, thanks for the answer. I am using Rails.cache now which is perfect fine. Just wanted to avoid the network overhead (it's not that big of a deal) and since it didn't work I got curious.
kolrie
A: 

Basically, you can't to that.

In development environment, with each request, your classes are reloaded. That means they're completely destroyed and recreated. The original class object is gone, a new one takes its place.

If you keep an object across requests, in the second request the object will still inherit from the original class, the one that has been removed. The constant that pointed to that class now points to a new class object, which may or may not be identical to the previous depending on whether you changed the class definition or plugins that affect it, but it'll still be a different class object in memory, and the old object will not know it should inherit from this new class object.

I assume if you run your application in production environment, it'll work.

kch