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.