Build Status Gem Version Code Climate

Extensions to normal Rails caching:


Cachext.configure do |config|
  config.cache = Rails.cache
  config.redis = Redis.current

key = [:foo, :bar, 1]
Cachext.fetch key, expires_in: 2.hours, default: "cow" do
  Faraday.get ""
  • Other services making the same call at the same time will wait for the first to complete, so only 1 call is made in a 2 hour window
  • A backup of the value is stored too, so if the service raises a Faraday::Error::ConnectionFailed we'll return the backup
  • If no backup exists but we got a ConnectionFailed, we'll return the default of "cow"
Record = :id
Cachext.multi [:foo, :bar], [1,2,3], expires_in: 5.minutes do |ids|
  data = JSON.parse Faraday.get("{ids.join(',')}")
  data.each_with_object({}) do |record, acc|
    acc[record["id"]] = record["id"]
# => { 1 =>, 2 =>, 3 => }
  • The passed block will be called with the ids that were not available in the cache. The return value of the block should either be a hash with keys of ids, or an array of objects that have id methods.
  • In the event of a server error (ie ConnectionFailed), backup values are used.

Configuration options

Cachext.config.cache = Rails.cache

Cachext expects a cache store that has the ActiveSupport::Cache interface, so that can be Memcache, Redis, FileStore, etc.

Cachext.config.redis = Redis.current

Cachext uses redis for locking (the Redlock gem under the hood), so we need at least Redis 2.8.

Cachext.config.raise_errors = false
Cachext.config.default_errors = [

By default Cachext will not re-raise the standard default errors. Setting this to true is helpful in a test environment. The default_errors are those caught as transient issues that a backup will be used for.

Cachext.config.not_found_errors = [Faraday::Error::ResourceNotFound]

If a NotFound exception is raised, the backup is not used, and any backup that exists will be deleted. Then the exception will be re-raised.

Cachext.config.default_expires_in = 60 # in seconds

The default TTL for values fetched. Only used for the "fresh" cache, not the backup (which has no TTL).

Cachext.config.max_lock_wait = 5 # in seconds

The most we'll wait for a lock to unlock. If it takes more than this value to get a lock (due to another service holding the lock while making the call), we'll fallback to the backup value.

Cachext.config.debug = ENV['CACHEXT_DEBUG'] == "true"

If debug is set to true (or you run your program/test with CACHEXT_DEBUG=true), you'll get lots of debug messages around the locking and whats going on. Very helpful for debugging :)

Cachext.config.heartbeat_expires = 2 # in seconds

If a process that holds a lock crashes, other processes will have to wait this many seconds for the lock to expire.

Cachext.config.error_logger = nil

If set to an object that responds to call, will call with any errors caught.

Cachext.config.failure_threshold = 3

Number of tries before tripping circuit breaker.

Cachext.config.breaker_timeout = 60

Time in seconds to wait before switching breaker to half-open.


Cachext.fetch key, options, &block

Available options:

  • expires_in: override for the default_expires_in, in seconds
  • default: object or proc that will be used as the default if no backup is found
  • errors: override for the default_errors: array of errors to catch and not reraise
  • reraise_errors: default true, if set to false NotFound errors will not be raised
  • not_found_error: (override) array of errors where we delete the backup and reraise
  • heartbeat_expires: (override) time in seconds for process heardbeat to expire
  • failure_threshold: (override) Number of tries before tripping circuit breaker
  • breaker_timeout: (override) time in seconds to wait before switching breaker to half-open
  • cache: use the first-level cache, defaults to true. If set to false, will always call the fallback, but if an error is raised, will use the last known good value.
Cachext.multi key_base, ids, options, &block

Available options:

  • expires_in: override for default_expires_in, in seconds
  • return_array: return an array instead of a hash. Will include missing records as Cachext::MissingRecord objects so you can deal with them.


After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to

Having trouble with a test? Set the CACHEXT_DEBUG environmental variable to "true" to get debug logs.


Bug reports and pull requests are welcome on GitHub at


The gem is available as open source under the terms of the MIT License.