tags:

views:

729

answers:

5

I'm looking for a ruby gem (or rails plugin) which abstracts the details of memcached in the same way that ActiveRecord abstracts the details of SQL. I am NOT looking for something to help cache ActiveRecord models in memcached. I'm sure there are approximately 4215 gems that will help with that problem.

Ideally what I'd like is to be able to do something like:

class Apple < MemcachedModel
# whatever else here
end

and then be able to do stuff like:

my_apple = Apple.find('some memcached key')

which would look up the JSON representation of this class in memcached and deserialize it. I'd also maybe be able to do things like:

my_apple.color = "red"

# persist changes back to memcached
my_apple.save 

# load any changes from memcached into local model
my_apple.update

It seems like someone must have scratched this itch by now and created something along these lines, but whenever I google for such a gem I just keep turning up thing which help cache AR models using memcached.

+3  A: 

I don't know about any Ruby ActiveRecord-like adapter for Memcached. A similar library would probably be hard to create because Memcached doesn't act as a relational database.

The result is that the library wouldn't be able to implement about the 80% of the features supported by ActiveRecord, so what's the benefit of such an implementation? You already have everything you need in Rails to work with memcache with a "CRUD" pattern.

Rails.cache.read('key')
Rails.cache.write('key', 'value')
Rails.cache.delete('key')
Rails.cache.increment('key', 5)
Rails.cache.fetch('key') { 'value' }

If you feel more comfortable, you can create a wrapper and proxy these methods with corresponding new/create/update/save/destroy methods. However, you would never be able to go beyond a basic CRUD system just because Memcached is not intended to be a relational database.

Simone Carletti
+2  A: 

It's fairly easy to implement.

require 'ostruct'
require 'active_support/cache'

class StoredStruct < OpenStruct
  attr_writer :store
  def self.store
    @store || superclass.store
  end

  def self.expand_key(key)
    'StoredStruct_' + (superclass == OpenStruct ? '' : "#{self}_") + key.to_s
  end

  def self.get_unique_id
    key = expand_key('unique_id')
    store.write(key, 0, :unless_exist => true)
    store.increment(key)
  end

  def self.save(instance)
    id = instance.id || get_unique_id
    store.write(expand_key(id), instance)
    id
  end

  def self.find(id)
    store.read(expand_key(id))
  end

  attr_reader :id

  def attributes
    @table
  end

  def attributes=(hash)
    @table = hash
  end

  def new_record?
    self.id.nil?
  end

  def save
    @id = self.class.save(self)
    true
  end

  def reload
    instance = self.class.find(self.id)
    self.attributes = instance.attributes unless self == instance
    self
  end
end

Use it like this:

# connect to memcached
StoredStruct.store = ActiveSupport::Cache::MemCacheStore.new("localhost:11211")

class Apple < StoredStruct
end

fruit = Apple.new
fruit.color = "red"
fruit.taste = "delicious"

fruit.id
#=> nil

fruit.save
#=> true
fruit.id
#=> 1

# to load any changes:
fruit.reload

Apple.find(1)
#=> fruit
mislav
Nice. I particularly like how you implemented get_unique_id. Ideally I'm looking to use an existing library rather than re-inventing the wheel, but if I did need to do this from scratch I would love to steal what you have here :)
Pete Hodgson
A: 

You may be looking for Nick Kallen's cache-money.

Sarah Mei
Thanks Sarah, but that's specifically what I'm NOT looking for - a caching library for ActiveRecord.
Pete Hodgson
Ah, I see I misunderstood your question. Do you want to dispense with ActiveRecord altogether and only store the objects in memcached? I'm curious what kinds of things you'll be storing.
Sarah Mei
Yeah, I have transient data which I get from another API. I want to cache the data locally so that I don't have to round-trip to this other API each time I service a request. The stuff I'm caching is account information stuff - objects like a User, an Organization, etc etc.
Pete Hodgson
+7  A: 

You can take a look at my moneta gem, which is an ORM'ish thing for all kinds of key-value-stores. You can see it at: http://github.com/wycats/moneta/tree/master

The basic idea behind moneta is that all KVSs should behave exactly like a subset of normal Ruby hashes. We support:

#[]
#[]=
#delete
#fetch
#key?
#store
#update_key
#clear

The store and update_key methods take an additional options hash which you can use thusly:

cache = Moneta::Memcache.new(:server => "localhost:11211", :namespace => "me")
cache.store("name", "wycats", :expires_in => 2)
cache.update_key("name", :expires_in => 10)

We support a large number of KVSs:

  • BerkeleyDB
  • CouchDB
  • DataMapper (which means any store supported by DM)
  • Files
  • LMC
  • Memcache
  • In-process memory
  • MongoDB
  • Redis
  • Tokyo Cabinet
  • Tokyo Tyrant
  • S3
  • SDBM
  • Files using XAttrs

Every store supports expiry, either natively (like in memcached) or using a standard module that emulates memcache-style expiry. The API is always identical and there is a shared spec that all adapters are run against to ensure compliance.

It is also quite easy to add your own adapter, which is why so many exist.

Yehuda Katz
That's a really interesting library. Thank you for posting it here!
Simone Carletti
Cool. thanks Yehuda, I'll check this out. I've been looking for an excuse to play with all these new persistent hash stores which are popping up.
Pete Hodgson