slick28
slick28

Reputation: 333

Rails 3.0.3 Rails.cache.read can't write to DB

I am playing around with the Rails 3 Rails.cache feature and when I write an ActiveRecord entry to cache, I cannot read it back, change the attributes then write to the database. I get a "TypeError: can't modify frozen hash". I used to use another memcache plugin, but I'm trying to switch over to Heroku, and it's incredibly annoying that I cannot save ActiveRecord entries I throw into Memcache. This will result in a lot of unnecessary DB reads to change small bits of information.

For example, I might do this in the database. Assuming the User model is:

User -> login:string and typing the following into rails c

user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user)
user2 = Rails.cache.read('user:login:test')
user2.login = 'test2'
TypeError: can't modify frozen hash
/app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:26:in `[]='
    /app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:26:in `write_attribute'
    /app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/dirty.rb:61:in `write_attribute'
    /app/.bundle/gems/ruby/1.8/gems/activerecord-3.0.3/lib/active_record/attribute_methods/write.rb:13:in `login='

Does anyone know how to solve this problem?

Upvotes: 2

Views: 614

Answers (2)

Ahmed Samir Shahin
Ahmed Samir Shahin

Reputation: 558

After getting the user2 object value from this line:

user2 = Rails.cache.read('user:login:test')

do the following:

dup_user = user2.dup # this will make a clone of the user2 object
dup_user.login = test_2

Then the "frozen hash" exception should not appear again.

Upvotes: 1

Simone Carletti
Simone Carletti

Reputation: 176472

When you store an object in cache, the object is frozen. This is one of the reasons why you should never store complex objects, like ActiveRecord models.

When you read from cache, the object is loaded, but it's still frozen. ActiveRecord tries to restore the object state, but it needs to update some internal attributes. The update will fail because the object is frozen.

Instead of storing the entire object, simply store its id and retrieve the user on-the-fly.

user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user.id)
user2 = User.find(Rails.cache.read('user:login:test'))
user2.login = 'test2'

This is a little bit less more efficient in terms of caching, but it's the way to go. In fact, you don't consider that in the meanwhile other attributes of the user might have been changed and your object no longer represents the current status.

Also, in this way your cache store won't act as a database and you'll be able to store more cache entries. IDs take less memory than an entire ActiveRecord instance.

Of course, you can use the #dup workaround, but it's just a trick. It's not the solution. You will still have problems with cache integrity as I explained above.

user = User.new
user.login = 'test'
user.save
Rails.cache.write('user:login:test', user)
user2 = Rails.cache.read('user:login:test').dup
user2.login = 'test2'

Upvotes: 0

Related Questions