method_missing error when caching an ActiveRecord model

I have a simple singleton cache that stores ActiveRecord model objects
for me so I'm not constantly hitting the DB for static data. This was
working great until I tried to add a method (any method really) to the
model, e.g:

class Tag << ActiveRecord::Base
  def is_main?
    logger.debug("is_main?")
    return some_attribute && some_other_attribute
  end
end

This method works fine when called from a unit test:

  def test_is_main
    tags = Tag.find_menus("__none")
    tags.each {|t|
      # Hit an accessor works fine
      assert t.is_menu?
      # Hit my method works fine
      assert t.is_main?
    }
  end

But if I pull the tag from the cache, the attributes are all there, but
the is_main? call gives method_missing, eg:

  def index
    home_page = MenuCache.home_page
    home_page2 = Tag.find_by_id(14)
    logger.debug("ConvsController.index(#{home_page.inspect})")
    logger.debug("ConvsController.index(#{home_page2.inspect})")

    home_page2.is_main?
    home_page.is_main # <= line #37
  end

Yields (before logging edited):

  Tag Load (0.010000) SELECT * FROM tags WHERE (tags.`id` = 14 )
LIMIT 1

ConvsController.index(#<Tag:0x6a69f30 @attributes={"name"=>"Home",
"updated_at"=>"2006-08-19 15:11:56", "a_key"=>"__home",
"parent_menu"=>"__none", "created_by"=>"1", "a_controller"=>"/pages",
"is_login_needed"=>"0", "id"=>"14", "list_view"=>"no_pref",
"show_view"=>"no_pref", "an_action"=>"index", "deletable"=>"0",
"inherit_security_from"=>nil, "ordering"=>"root_id DESC, lft",
"position"=>"1", "is_menu"=>"1", "created_at"=>"2006-08-19 15:11:56"}>)

ConvsController.index(#<Tag:0x6796150 @attributes={"name"=>"Home",
"updated_at"=>"2006-08-19 15:11:56", "a_key"=>"__home",
"parent_menu"=>"__none", "created_by"=>"1", "a_controller"=>"/pages",
"is_login_needed"=>"0", "id"=>"14", "list_view"=>"no_pref",
"show_view"=>"no_pref", "an_action"=>"index", "deletable"=>"0",
"inherit_security_from"=>nil, "ordering"=>"root_id DESC, lft",
"position"=>"1", "is_menu"=>"1", "created_at"=>"2006-08-19 15:11:56"}>)

is_main?()

NoMethodError (undefined method `is_main?' for #<Tag:0x6a69f30>):

C:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1792:in
`method_missing'
    /app/controllers/pages_controller.rb:37:in `index'

So where did my method disappear to?

Thanks in advance, Brittain

Hi~

Ezra Zygmuntowicz wrote:

Hi~

>
> I have a simple singleton cache that stores ActiveRecord model objects
> for me so I'm not constantly hitting the DB for static data. This was
> working great until I tried to add a method (any method really) to the
> model, e.g:
>
> class Tag << ActiveRecord::Base
> def is_main?
> logger.debug("is_main?")
> return some_attribute && some_other_attribute
> end
> end
>
> This method works fine when called from a unit test:
>
> def test_is_main
> tags = Tag.find_menus("__none")
> tags.each {|t|
> # Hit an accessor works fine
> assert t.is_menu?
> # Hit my method works fine
> assert t.is_main?
> }
> end
>
> But if I pull the tag from the cache, the attributes are all there,
> but
> the is_main? call gives method_missing, eg:
>
> def index
> home_page = MenuCache.home_page
> home_page2 = Tag.find_by_id(14)
> logger.debug("ConvsController.index(#{home_page.inspect})")
> logger.debug("ConvsController.index(#{home_page2.inspect})")
>
> home_page2.is_main?
> home_page.is_main # <= line #37
> end
>
> Yields (before logging edited):
>
> Tag Load (0.010000) SELECT * FROM tags WHERE (tags.`id` = 14 )
> LIMIT 1
>
> ConvsController.index(#<Tag:0x6a69f30 @attributes={"name"=>"Home",
> "updated_at"=>"2006-08-19 15:11:56", "a_key"=>"__home",
> "parent_menu"=>"__none", "created_by"=>"1", "a_controller"=>"/pages",
> "is_login_needed"=>"0", "id"=>"14", "list_view"=>"no_pref",
> "show_view"=>"no_pref", "an_action"=>"index", "deletable"=>"0",
> "inherit_security_from"=>nil, "ordering"=>"root_id DESC, lft",
> "position"=>"1", "is_menu"=>"1", "created_at"=>"2006-08-19
> 15:11:56"}>)
>
> ConvsController.index(#<Tag:0x6796150 @attributes={"name"=>"Home",
> "updated_at"=>"2006-08-19 15:11:56", "a_key"=>"__home",
> "parent_menu"=>"__none", "created_by"=>"1", "a_controller"=>"/pages",
> "is_login_needed"=>"0", "id"=>"14", "list_view"=>"no_pref",
> "show_view"=>"no_pref", "an_action"=>"index", "deletable"=>"0",
> "inherit_security_from"=>nil, "ordering"=>"root_id DESC, lft",
> "position"=>"1", "is_menu"=>"1", "created_at"=>"2006-08-19
> 15:11:56"}>)
>
> is_main?()
>
>
> NoMethodError (undefined method `is_main?' for #<Tag:0x6a69f30>):
>
> C:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/
> active_record/base.rb:1792:in
> `method_missing'
> /app/controllers/pages_controller.rb:37:in `index'
>
>
> So where did my method disappear to?
>
> Thanks in advance, Brittain

  Can you show your caching code? How are you storing items in the
cache? Are you using marshal? Also if you run more then one mongrel
or fcgi how will you make sure that you always get the same cache
object to work with?

Thanks for the response Ezra. I've attached the cache below, they are
simple hashes with object references. We've been using a couple others
of similar design, this is the first we've ever added methods to the
stored models.

We just discovered a short while ago when we started using
mongrel_cluster (which you appear already aware of) that we CANNOT get
a handle to a specific cache. This is only an issue when we want to do
a cache reload (happens infrequently), otherwise this data is truly
static; however, if you're a solution for that, we'd welcome hearing
it.

Code below, edited for clarity:

Just to get the obvious out of the way, did you restart your webserver after you made changes to the cached models? That could make this happen. Other then that I would think this should work fine. If you need to have a cache like this that gets updated during the app running you need to put it into its own process that all your rails backends can talk to. I have caching built into my backgroundrb plugin[1] that may be useful to you. But your solution should work as it is. If its still doesn't work even after a server restart then perhaps you need to serialize the models before storage and unserialize them on the way out. Use Marshal.dump(obj) and Marshal.load(obj) for this.

Cheers-
-Ezra

[1] http://backgroundrb.rubyforge.org

Tried the restart many times without success. I'll give the marshal
solution a shot and post back.

We're implementing BackgroundRB for a number of other features so I'll
make sure to use it to solve the in process problem.

In the meantime, if anyone else has any ideas, I'd welcome hearing
them.

Thanks.