CaChing

CaChing is a write-through and read-through caching library for ActiveRecord 3.1+.

That means that when you read from the database, your results are stored in the cache (read-through). When you write to the database, whatever is written to the database is also written to the cache (write-through). If the results are already in the cache, great, they're read straight from there on a read, and updated on a write.

This is definitely still alpha-level software. It's just something I thought should exist; I'm not using it in production anywhere. It could blow up in totally unexpected, but nevertheless exciting, ways. If it does fall over, let me know.

Take a look at SPEC.md for what's planned for v1.

Getting started

In your Gemfile:

gem 'ca_ching'
# gem 'ca_ching', :git => 'git://github.com/ahlatimer/ca_ching.git' # Track git repo

In an initializer:

CaChing.configure do |config|
  config.cache = CaChing::Adapters::Redis.new
  config.enabled = true # defaults to true
end

Defining what to cache

The simplest case is to add index :field_name to your model. From then on, any query that does a find based on that field will get cached.

class Person < ActiveRecord::Base
  index :id
  index :first_name
  index :last_name

  has_many :addresses
end

class Address < ActiveRecord::Base  
  # Let's you find by associations (person.addresses.all)
  index :person_id

  belongs_to :person
end

Using the cache

If you've defined your indices, everything should work without any additional effort. Doing Person.where(:first_name => 'Andrew') should hit the cache and return what is there, or miss and pull the data into the cache.

Queries supported

Generally anything involving equality is supported by CaChing. Currently, only single fields can be found (e.g., Person.where(:name => 'Andrew') will be cached, Person.where(:name => 'Andrew', :age => 22) is not). There is some plumbing for adding support for the latter, and support will be introduced as soon as possible. I also have plans to add limited support for comparators (i.e., <, <=, >, >=).

Anything including joins, includes, OR, and inequality (!=) are not supported, nor do I have any plans for adding support.

For example, these queries are supported:

Person.where(:first_name => 'Andrew')
Person.find_by_first_name('Andrew')
Person.find_all_by_first_name('Andrew')

These queries are not:

Person.where('first_name = ? OR first_name = ?', 'Andrew', 'David')
Person.where('first_name != ?', 'Andrew')
Person.where(:first_name => 'Andrew').order('created_at DESC') # not supported because first_name isn't sorted by anything
Person.where('id >= 200')
Person.where('created_at >= ?', Date.today - 1).where(:first_name => 'Andrew')
Person.where('created_at <= ?', Date.today - 1).where(:first_name => 'Andrew').limit(10).order('created_at desc')

Caveats

Order

Order is not currently supported, although there will be some order functionality in the next version.

Composite keys and queries against multiple fields

Because of the awesomeness from Redis, composite keys aren't necessary for queries with multiple fields. If all of the fields are indexed, CaChing will try to use the cache to build the results.

Redis requires that sorted set intersections be stored in a resulting set. If the composite key is not specified for a query against those fields, that resulting set will be destroyed as soon as the results are read. If the composite key is specified, CaChing will keep the resulting set.

While there is support for this in the Redis adapter, there isn't any support in the ActiveRecord ties as I haven't decided how, exactly, I'd like to add this.

Thar be dragons

While I've tried to outline the issues here (and write failing specs for them), realize that this is still very much alpha software. There are probably a number of unknown unknowns that I just haven't uncovered yet. If you find them, please be sure to let me know!

Ruby/Rails versions supported

Ruby 1.9.2 and Rails 3.1+ are officially supported. I try to stick to Ruby 1.8.7 syntax, so it may be supported, but use it with the understanding that you are doing so at your own risk.

Patches and Issues

I'd love the help! If you find an issue, please report it on the Github issues page. If you fix an issue, please include a spec that illustrates the issue. If you submit a feature, include thorough specs. Don't bump up the version in your pull request. If you want to keep different versions in your fork, feel free, but please do not include them in your pull request.

Acknowledgments

Inspired by (and a bit of code copied from) Cache Money.