AnyCache · Gem Version Build Status Coverage Status

AnyCache - a simplest cache wrapper that provides a minimalistic generic interface for all well-known cache storages and includes a minimal set of necessary operations: fetch, read, write, delete, expire, persist, exist?, clear, increment, decrement.

Supported clients:


Installation

gem 'any_cache'
bundle install
# --- or ---
gem install any_cache
require 'any_cache'

Usage / Table of Contents


Creation

To instantiate AnyCache instance you have to provide a client. Client - an independent driver that works with a corresponding cache storage (external dependency).

Supported clients:

  • Redis
  • Redis::Store
  • Dalli::Client
  • ActiveSupport::Cache::RedisCacheStore
  • ActiveSupport::Cache::MemCacheStore
  • ActiveSupport::Cache::FileStore
  • ActiveSupport::Cache::MemoryStore

AnyCache can be instantiated by two ways:

  • with explicit client object instantiated manually (read);
  • via configuration (read);

Manual creation

Custom instantiation with explicit client objects:

# 1) create client object
client = Redis.new(...)
# -- or --
client = Redis::Store.new(...)
# -- or --
client = Dalli::Client.new(...)
# -- or --
client = ActiveSupport::Cache::RedisCacheStore.new(...)
# --- or ---
client = ActiveSupport::Cache::MemCacheStore.new(...)
# -- or --
client = ActiveSupport::Cache::FileStore.new(...)
# -- or --
client = ActiveSupport::Cache::MemoryStore.new(...)

# 2) build AnyCache instance
any_cache = AnyCache.build(client) # => <AnyCache:0x00007f990527f268 ...>

Config-based creation

You can configure AnyCache globally or create subclasses and configure each of them. After that storage instantiation works via .build method without explicit attributes.

  • AnyCache.configure is used for configuration;
  • config.driver is used for determine which client should be used;
  • config.__driver_name__.options stores client-related options;

Supported drivers:

AnyCache with Redis:
require 'redis'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :redis
  conf.redis.options = { ... } # Redis-related options
end

AnyCache.build
AnyCache with Redis::Store:
require 'redis-store'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :redis_store
  conf.redis_store.options = { ... } # Redis::Store-related options
end

AnyCache.build
AnyCache with Dalli::Client:
require 'dalli'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :dalli
  conf.dalli.servers = ... # string or array of strings
  conf.dalli.options = { ... } # Dalli::Client-related options
end

AnyCache.build
AnyCache with ActiveSupport::Cache::RedisCacheStore:
require 'active_support'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :as_redis_cache_store
  conf.as_redis_cache_store.options = { ... } # ActiveSupport::Cache::RedisCacheStore-related options
end

AnyCache.build
AnyCache with ActiveSupport::Cache::MemCacheStore:
require 'active_support'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :as_mem_cache_store
  conf.as_memory_store.servers = ... # string or array of strings
  conf.as_memory_store.options = { ... } # ActiveSupport::Cache::MemCacheStore-related options
end

AnyCache.build
AnyCache with ActiveSupport::Cache::FileStore:
require 'active_support'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :as_file_store
  conf.as_file_store.cache_path = '/path/to/cache'
  conf.as_file_store.options = { ... } # ActiveSupport::Cache::FileStore-related options
end

AnyCache.build
AnyCache with ActiveSupport::Cache::MemoryStore:
require 'activesupport'
require 'any_cache'

AnyCache.configure do |conf|
  conf.driver = :as_memory_store
  conf.as_memory_store.options = { ... } # ActiveSupport::Cache::MemoryStore-related options
end

AnyCache.build

Many cache storages

You can inherit AnyCache class and create and configure as many cache storages as you want:

class RedisCache < AnyCache
  configure do |conf|
    conf.driver = :redis
  end
end

class DalliCache < AnyCache
  configure do |conf|
    conf.driver = :dalli
  end
end

redis_cache = RedisCache.build
dalli_cache = DalliCache.build

Custom cache clients

If you want to use your own cache client implementation, you should provide an object that responds to:

  • #fetch(*key, [**options]) (doc)
  • #read(key, [**options]) (doc)
  • #write(key, value, [**options]) (doc)
  • #delete(key, [**options]) (doc)
  • #increment(key, amount, [**options]) (doc)
  • #decrement(key, amount, [**options]) (doc)
  • #expire(key, [**options]) (doc)
  • #persist(key, [**options]) (doc)
  • #exist?(key, [**options]) (doc)
  • #clear([**options]) (doc)
class MyCacheClient
  # ...

  def read(key, **)
    # ...
  end

  def write(key, value, **)
    # ...
  end

  # ...
end

AnyCache.build(MyCacheClient.new)

Operations

AnyCache provides a following operation set:


Fetch

  • AnyCache#fetch(key, [force:], [expires_in:], [&block])
    • works in ActiveSupport::Cache::Store#fetch-manner;
    • fetches data from the cache, using the given key;
    • if there is data in the cache with the given key, then that data is returned;
    • if there is no such data in the cache (a cache miss), then nil will be returned:
      • if a block has been passed, that block will be passed the key and executed in the event of a cache miss;
      • the return value of the block will be written to the cache under the given cache key, and that return value will be returned;
# --- entry exists ---
any_cache.fetch("data") # => "some_data"
any_cache.fetch("data") { "new_data" } # => "some_data"

# --- entry does not exist ---
any_cache.fetch("data") # => nil
any_cache.fetch("data") { "new_data" } # => "new_data"
any_cache.fetch("data") # => "new_data"

# --- new entry with expiration time ---
any_cache.fetch("data") # => nil
any_cache.fetch("data", expires_in: 8) { "new_data" } # => "new_data"
any_cache.fetch("data") # => "new_data"
# ...sleep 8 seconds...
any_cache.fetch("data") # => nil

# --- force update/rewrite ---
any_cache.fetch("data") # => "some_data"
any_cache.fetch("data", expires_in: 8, force: true) { "new_data" } # => "new_data"
any_cache.fetch("data") # => "new_data"
# ...sleep 8 seconds...
any_cache.fetch("data") # => nil

Read

  • AnyCache#read(key) - get an entry value from the cache storage
# --- entry exists ---
any_cache.read("data") # => "some_data"

# --- entry doesnt exist ---
any_cache.read("data") # => nil

Write

  • AnyCache#write(key, value, [expires_in:]) - write a new entry to the cache storage
# --- permanent entry ---
any_cache.write("data", 123)

# --- temporal entry (expires in 60 seconds) ---
any_cache.write("data", 123, expires_in: 60)

Delete

  • AnyCache#delete(key) - remove entry from the cache storage
any_cache.delete("data")

Increment

  • AnyCache#increment(key, amount = 1, [expires_in:]) - increment entry's value by the given amount and set the new expiration time if needed
# --- increment existing entry ---
any_cache.write("data", 1)

# --- increment by default value (1) ---
any_cache.increment("data") # => 2

# --- increment by custom value ---
any_cache.increment("data", 12) # => 14

# --- increment and expire after 31 seconds
any_cache.incrmeent("data", expires_in: 31) # => 15

# --- increment nonexistent entry (create new entry) ---
any_cache.increment("another_data", 5, expires_in: 5) # => 5

Decrement

  • AnyCache#decrement(key, amount = 1, [expires_in:]) - decrement entry's value by the given amount and set the new expiration time if needed
# --- decrement existing entry ---
any_cache.write("data", 15)

# --- decrement by default value (1) ---
any_cache.decrement("data") # => 14

# --- decrement by custom value ---
any_cache.decrement("data", 10) # => 4

# --- decrement and expire after 5 seconds
any_cache.decrememnt("data", expirs_in: 5) # => 3

# --- decrement nonexistent entry (create new entry) ---
any_cache.decrememnt("another_data", 2, expires_in: 10) # => -2 (or 0 for Dalli::Client)

Expire

  • AnyCache#expire(key, [expires_in:]) - expire entry immediately or set the new expiration time
# --- expire immediately ---
any_cache.expire("data")

# --- set new expiration time (in seconds) --
any_cache.expire("data", expires_in: 36)

Persist

  • AnyCache#persist(key) - change entry's expiration time to permanent
# --- create temporal entry (30 seconds) ---
any_cache.write("data", { a: 1 }, expires_in: 30)

# --- remove entry expiration (make it permanent) ---
any_cache.persist("data")

Existence

  • AnyCache#exist?(key) - determine if an entry exists
# --- entry exists ---
any_cache.exist?("data") # => true

# --- entry does not exist ---
any_cache.exist?("another-data") # => false

Clear

  • AnyCache#clear() - clear cache database
# --- prepare cache data ---
any_cache.write("data", { a: 1, b: 2 })
any_cache.write("another_data", 123_456)

any_cache.read("data") # => { a: 1, b: 2 }
any_cache.read("another_data") # => 123_456

# --- clear cache ---
any_cache.clear

any_cache.read("data") # => nil
any_cache.read("another_data") # => nil

Build

bin/rspec --test-redis # run specs with Redis
bin/rspec --test-redis-store # run specs with Redis::Store
bin/rspec --test-dalli # run specs with Dalli::Client
bin/rspec --test-as-file-store # run specs with ActiveSupport::Cache::FileStore
bin/rspec --test-as-memory-store # run specs with ActiveSupport::Cache::MemoryStore
bin/rspec --test-as-redis-cache-store # run specs with ActiveSupport::Cache::RedisCacheStore
bin/rspec --test-as-mem-cache-store # run specs with ActiveSupport::Cache::MemCacheStore

Contributing

  • Fork it (https://github.com/0exp/any_cache/fork)
  • Create your feature branch (git checkout -b feature/my-new-feature)
  • Commit your changes (git commit -am 'Add some feature')
  • Push to the branch (git push origin feature/my-new-feature)
  • Create new Pull Request

License

Released under MIT License.

Authors

Created by Rustam Ibragimov