Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/0exp/any_cache
A powerful cache wrapper that provides a minimalistic generic interface for all well-known cache storages. You can use any cache implementation in ANY project easily.
https://github.com/0exp/any_cache
activesupport-cache cache dalli dalli-cache-store file-cache-store memcached memory-cache-store rails-cache redis redis-cache-store redis-store ruby-cache
Last synced: about 2 months ago
JSON representation
A powerful cache wrapper that provides a minimalistic generic interface for all well-known cache storages. You can use any cache implementation in ANY project easily.
- Host: GitHub
- URL: https://github.com/0exp/any_cache
- Owner: 0exp
- License: mit
- Created: 2018-08-19T21:26:02.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2021-04-27T20:58:12.000Z (over 3 years ago)
- Last Synced: 2024-10-12T09:20:17.871Z (2 months ago)
- Topics: activesupport-cache, cache, dalli, dalli-cache-store, file-cache-store, memcached, memory-cache-store, rails-cache, redis, redis-cache-store, redis-store, ruby-cache
- Language: Ruby
- Homepage:
- Size: 261 KB
- Stars: 5
- Watchers: 1
- Forks: 1
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# AnyCache · [![Gem Version](https://badge.fury.io/rb/any_cache.svg)](https://badge.fury.io/rb/any_cache) [![Build Status](https://travis-ci.org/0exp/any_cache.svg?branch=master)](https://travis-ci.org/0exp/any_cache) [![Coverage Status](https://coveralls.io/repos/github/0exp/any_cache/badge.svg?branch=master)](https://coveralls.io/github/0exp/any_cache?branch=master)
AnyCache - a powerful 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`, `fetch_multi`, `read_multi`, `write_multi`, `delete_matched`, `expire`, `persist`, `exist?`, `clear`, `cleanup`, `increment`, `decrement`.Supported clients:
- `Redis` ([gem redis](https://github.com/redis/redis-rb)) ([redis storage](https://redis.io/))
- `Redis::Store` ([gem redis-store](https://github.com/redis-store/redis-store)) ([redis storage](https://redis.io/))
- `Dalli::Client` ([gem dalli](https://github.com/petergoldstein/dalli)) ([memcached storage](https://memcached.org/))
- `ActiveSupport::Cache::RedisCacheStore` ([gem activesupport](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache/redis_cache_store.rb)) ([redis cache storage](https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html))
- `ActiveSupport::Cache::DalliStore` ([gem dalli](https://github.com/petergoldstein/dalli)) ([dalli store](https://github.com/petergoldstein/dalli/blob/master/lib/active_support/cache/dalli_store.rb))
- `ActiveSupport::Cache::MemCacheStore` ([gem activesupport](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache/mem_cache_store.rb)) ([memcache storage](https://api.rubyonrails.org/classes/ActiveSupport/Cache/MemCacheStore.html))
- `ActiveSupport::Cache::FileStore` ([gem activesupport](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache/file_store.rb)) ([file storage](https://api.rubyonrails.org/classes/ActiveSupport/Cache/FileStore.html))
- `ActiveSupport::Cache::MemoryStore` ([gem activesupport](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache/memory_store.rb)) ([in memory storage](https://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html))- [Coming Soon] File-based configuration;
- [Coming Soon] Simple in-memory hash-based cache client
- [Coming Soon] Simple key-value storage based on PostgreSQL
- [Coming Soon] Support for `ActiveSupport::Cache::NullStore`
- [Coming Soon] Naive `NullStore` implementation;---
## Installation
```ruby
gem 'any_cache'
``````shell
bundle install
# --- or ---
gem install any_cache
``````ruby
require 'any_cache'
```---
## Usage / Table of Contents
- [Creation](#creation)
- [Manual creation](#manual-creation)
- [Config-based creation](#config-based-creation)
- [AnyCache with Redis](#anycache-with-redis)
- [AnyCache with Redis::Store](#anycache-with-redisstore)
- [AnyCache with Dalli::Client](#anycache-with-dalliclient)
- [AnyCache with ActiveSupport::Cache::RedisCacheStore](#anycache-with-activesupportcacherediscachestore)
- [AnyCache with ActiveSupport::Cache::DalliStore](#anycache-with-activesupportcachedallistore)
- [AnyCache with ActiveSupport::Cache::MemCacheStore](#anycache-with-activesupportcachememcachestore)
- [AnyCache with ActiveSupport::Cache::FileStore](#anycache-with-activesupportcachefilestore)
- [AnyCache with ActiveSupport::Cache::MemoryStore](#anycache-with-activesupportcachememorystore)
- [Many cache storages](#many-cache-storages)
- [Custom cache clients](#custom-cache-clients)
- [Logging](#logging)
- [Operations](#operations)
- [Fetch](#fetch) / [Fetch Multi](#fetch-multi)
- [Read](#read) / [Read Multi](#read-multi)
- [Write](#write) / [Write Multi](#write-multi)
- [Delete](#delete) / [Delete Matched](#delete-matched)
- [Increment](#increment) / [Decrement](#decrement)
- [Expire](#expire)
- [Persist](#persist)
- [Existence](#existence)
- [Clear](#clear)
- [Cleanup](#cleanup)
- [Plugins](#plugins)
- [Roadmap](#roadmap)---
### 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::DalliStore`
- `ActiveSupport::Cache::MemCacheStore`
- `ActiveSupport::Cache::FileStore`
- `ActiveSupport::Cache::MemoryStore``AnyCache` can be instantiated by two ways:
- with explicit client object instantiated manually ([read](#manual-creation));
- via configuration ([read](#config-based-creation));#### Manual creation
Custom instantiation with explicit client objects:
```ruby
# 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::DalliStore.new(...)
# -- or --
client = ActiveSupport::Cache::MemCacheStore.new(...)
# -- or --
client = ActiveSupport::Cache::FileStore.new(...)
# -- or --
client = ActiveSupport::Cache::MemoryStore.new(...)# 2) build AnyCache instance
cache_store = AnyCache.build(client) # =>
```#### 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:
- `:redis` - [Redis](#anycache-with-redis);
- `:redis_store` - [Redis::Client](#anycache-with-redisstore);
- `:dalli` - [Dalli::Client](#anycache-with-dalliclient);
- `:as_redis_cache_store` - [ActiveSupport::Cache::RedisCacheStore](#anycache-with-activesupportcacherediscachestore);
- `:as_dalli_store` - [ActiveSupport::Cache::DalliStore](#anycache-with-activesupportcachedallistore);
- `:as_mem_cache_store` - [ActiveSupport::Cache::MemCacheStore](#anycache-with-activesupportcachememcachestore);
- `:as_file_store` - [ActiveSupport::Cache::FileStore](#anycache-with-activesupportcachefilestore);
- `:as_memory_store` - [ActiveSupport::Cache::MemoryStore](#anycache-with-activesupportcachememorystore);##### `AnyCache` with `Redis`:
```ruby
require 'redis'
require 'any_cache'AnyCache.configure do |conf|
conf.driver = :redis
conf.redis.options = { ... } # Redis-related options
endcache_store = AnyCache.build
```##### `AnyCache` with `Redis::Store`:
```ruby
require 'redis-store'
require 'any_cache'AnyCache.configure do |conf|
conf.driver = :redis_store
conf.redis_store.options = { ... } # Redis::Store-related options
endcache_store = AnyCache.build
```##### `AnyCache` with `Dalli::Client`:
```ruby
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
endcache_store = AnyCache.build
```##### `AnyCache` with `ActiveSupport::Cache::RedisCacheStore`:
```ruby
require 'redis'
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
endcache_store = AnyCache.build
```##### `AnyCache` with `ActiveSupport::Cache::DalliStore`:
```ruby
require 'dalli'
require 'active_support'
require 'any_cache'AnyCache.enable_patch!(:dalli_store) # NOTE: actual for Dalli <= 2.7.8
AnyCache.configure do |conf|
conf.driver = :as_dalli_store
conf.as_dalli_store.servers = ... # string or array of strings
conf.as_dalli_store.options = { ... } # ActiveSupport::Cache::DalliStore-related options
endcache_store = AnyCache.build
```##### `AnyCache` with `ActiveSupport::Cache::MemCacheStore`:
```ruby
require 'active_support'
require 'any_cache'AnyCache.configure do |conf|
conf.driver = :as_mem_cache_store
conf.as_mem_cache_store.servers = ... # string or array of strings
conf.as_mem_cache_store.options = { ... } # ActiveSupport::Cache::MemCacheStore-related options
endcache_store = AnyCache.build
```##### `AnyCache` with `ActiveSupport::Cache::FileStore`:
```ruby
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
endcache_store = AnyCache.build
```##### `AnyCache` with `ActiveSupport::Cache::MemoryStore`:
```ruby
require 'activesupport'
require 'any_cache'AnyCache.configure do |conf|
conf.driver = :as_memory_store
conf.as_memory_store.options = { ... } # ActiveSupport::Cache::MemoryStore-related options
endcache_store = AnyCache.build
```#### Many cache storages
You can inherit `AnyCache` class and create and configure as many cache storages as you want:
```ruby
class RedisCache < AnyCache
configure do |conf|
conf.driver = :redis
end
endclass DalliCache < AnyCache
configure do |conf|
conf.driver = :dalli
end
endredis_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](#fetch))
- `#fetch_multi(*keys, [**options])` ([doc](#fetch-multi))
- `#read(key, [**options])` ([doc](#read))
- `#read_multi(*keys, [**options])` ([doc](#read-multi))
- `#write(key, value, [**options])` ([doc](#write))
- `#write_multi(entries, [**options])` ([doc](#write-multi))
- `#delete(key, [**options])` ([doc](#delete))
- `#delete_matched(pattern, [**options])` ([doc](#delete-matched))
- `#increment(key, amount, [**options])` ([doc](#increment))
- `#decrement(key, amount, [**options])` ([doc](#decrement))
- `#expire(key, [**options])` ([doc](#expire))
- `#persist(key, [**options])` ([doc](#persist))
- `#exist?(key, [**options])` ([doc](#existence))
- `#clear([**options])` ([doc](#clear))
- `#cleanup([**options])` ([doc](#cleanup))```ruby
class MyCacheClient
# ...def read(key, **)
# ...
enddef write(key, value, **)
# ...
end# ...
endAnyCache.build(MyCacheClient.new)
```---
### Logging
AnyCache logs all its operations. By default, `AnyCache` uses a simple `STDOUT` logger with `Logger::INFO` level.
Logging is performed with level configured in logger object.Logger configuration:
```ruby
# --- use your own logger ---
AnyCache.configure do |conf|
conf.logger = MyLoggerObject.new
end# --- disable logging ---
AnyCache.configure do |conf|
conf.logger = nil
end# --- (used by default) ---
AnyCache.configure do |conf|
conf.logger = AnyCache::Logging::Logger.new(STDOUT)
end# --- (your cache client inherited from AnyCache) ---
YourCacheClient.configure do |conf|
# same configs as above
end
```Log message format:
```shell
[AnyCache/Activity]: performed operation with
params: INSPECTED_ARGUMENTS and options: INSPECTED_OPTIONS
```- progname
- `CACHER_NAME` - class name of your cache class;
- `OPERATION_NAME` - performed operation (`read`, `write`, `fetch` and etc);
- message
- `INSPECTED_ARGUMENTS` - a set of operation arguments;
- `INSPECTED_OPTIONS` - a set of operation options;```ruby
any_cache.write("data", 123, expires_in: 60)
# I, [2018-09-07T10:04:56.649960 #15761] INFO -- [AnyCache/Activity]: performed operation with attributes: ["data", 123] and options: {:expires_in=>60}.any_cache.clear
# I, [2018-09-07T10:05:26.999847 #15761] INFO -- [AnyCache/Activity]: performed operation with attributes: [] and options: {}.
```## Operations
`AnyCache` provides a following operation set:
- [fetch](#fetch) / [fetch_multi](#fetch-multi)
- [read](#read) / [read_multi](#read-multi)
- [write](#write) / [write_multi](#write-multi)
- [delete](#delete) / [delete_matched](#delete-matched)
- [increment](#increment) / [decrement](#decrement)
- [expire](#expire)
- [persist](#persist)
- [clear](#clear)
- [exist?](#existence)---
### Fetch
- `AnyCache#fetch(key, [force:], [expires_in:], [&fallback])`
- works in `ActiveSupport::Cache::Store#fetch`-manner;
- fetches data from the cache using the given key;
- if a `fallback` block has been passed and data with the given key does not exist - that block
will be called with the given key and the return value will be written to the cache;
- use `raw: true` if you want to fetch incrementable/decrementable entry;```ruby
# --- entry exists ---
cache_store.fetch("data") # => "some_data"
cache_store.fetch("data") { "new_data" } # => "some_data"# --- entry does not exist ---
cache_store.fetch("data") # => nil
cache_store.fetch("data") { |key| "new_data" } # => "new_data"
cache_store.fetch("data") # => "new_data"# --- new entry with expiration time ---
cache_store.fetch("data") # => nil
cache_store.fetch("data", expires_in: 8) { |key| "new_#{key}" } # => "new_data"
cache_store.fetch("data") # => "new_data"
# ...sleep 8 seconds...
cache_store.fetch("data") # => nil# --- force update/rewrite ---
cache_store.fetch("data") # => "some_data"
cache_store.fetch("data", expires_in: 8, force: true) { |key| "new_#{key}" } # => "new_data"
cache_store.fetch("data") # => "new_data"
# ...sleep 8 seconds...
cache_store.fetch("data") # => nil
```---
### Fetch Multi
- `AnyCache#fetch_multi(*keys, [force:], [expires_in:], [&fallback])`
- get a set of entries in hash form from the cache storage using given keys;
- works in `#fetch` manner but with a series of entries;
- nonexistent entries will be fetched with `nil` values;
- use `raw: true` if you want to fetch incrementable/decrementable entries;```ruby
# --- fetch entries ---
cache_store.fetch_multi("data", "second_data", "last_data")
# => returns:
{
"data" => "data", # existing entry
"second_data" => nil, # nonexistent entry
"last_data" => nil # nonexistent entry
}# --- fetch etnries and define non-existent entries ---
cache_store.fetch_multi("data", "second_data", "last_data") { |key| "new_#{key}" }
# => returns:
{
"data" => "data", # entry with OLD value
"second_data" => "new_second_data", # entry with NEW DEFINED value
"last_data" => "new_last_data" # entry with NEW DEFINED value
}# --- force rewrite all entries ---
cache_store.fetch_multi("data", "second_data", "last_data", force: true) { |key| "force_#{key}" }
# => returns
{
"data" => "force_data", # entry with REDEFINED value
"second_data" => "force_second_data", # entry with REDEFINED value
"last_data" => "force_last_data" # entry with REDEFINED value
}
```---
### Read
- `AnyCache#read(key)` - get an entry value from the cache storage
- pass `raw: true` if you want to read incrementable/decrementable entries;```ruby
# --- entry exists ---
cache_store.read("data") # => "some_data"# --- entry doesnt exist ---
cache_store.read("data") # => nil# --- read incrementable/decrementable entry ---
cache_store.read("data", raw: true) # => "2" (for example)
```---
### Read Multi
- `AnyCache#read_multi(*keys)`
- get entries from the cache storage in hash form;
- nonexistent entries will be fetched with `nil` values;
- pass `raw: true` if you want to read incrementable/decrementable entries;```ruby
cache_store.read_multi("data", "another_data", "last_data", "super_data")
# => returns
{
"data" => "test", # existing entry
"another_data" => nil, # nonexistent entry
"last_data" => "some_data", # exisitng enry
"super_data" => nil # existing entry
}# --- read incrementable/decrementable entries ---
cache_store.read_multi("data", "another_data", raw: true)
# => returns
{
"data" => "1",
"another_data" => "2",
}
```---
### Write
- `AnyCache#write(key, value, [expires_in:])` - write a new entry to the cache storage;
- pass `raw: true` if you want to store incrementable/decrementable entries;```ruby
# --- permanent entry ---
cache_store.write("data", 123)# --- temporal entry (expires in 60 seconds) ---
cache_store.write("data", 123, expires_in: 60)# --- incrementable/decrementable entry ---
cache_store.write("data", 123, raw: true)
```---
### Write Multi
- `AnyCache#write_multi(**entries)` - write a set of permanent entries to the cache storage;
- pass `raw: true` if you want to store incrementable/decrementable entries;```ruby
cache_store.write_multi("data" => "test", "another_data" => 123)# --- incrementable/decrementable entries ---
cache_store.write_multi("data" => 1, "another_data" => 2, raw: true)
```---
### Delete
- `AnyCache#delete(key)` - remove entry from the cache storage;
```ruby
cache_store.delete("data")
```---
### Delete Matched
- `AnyCache#delete_matched(pattern)`
- removes all entries with keys matching the pattern;
- currently unsupported: `:dalli`, `:as_mem_cache_store`, `:as_dalli_store`;```ruby
# --- using a regepx ---
cache_store.delete_matched(/\A*test*\z/i)# --- using a string ---
cache_store.delete_matched("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;
- can increment only nonexistent entries OR entries that were written with `raw: true` option;```ruby
# --- increment existing entry ---
cache_store.write("data", 1, raw: true) # you must provide :raw => true for incrementable entries# --- increment by default value (1) ---
cache_store.increment("data") # => 2# --- increment by custom value ---
cache_store.increment("data", 12) # => 14# --- increment and expire after 31 seconds
cache_store.incrmeent("data", expires_in: 31) # => 15# --- increment nonexistent entry (create new entry) ---
cache_store.increment("another_data", 5, expires_in: 5) # => 5# --- read incrementable entry ---
cache_store.read("data", raw: true) # you must provide :raw => true for incrementable entries
```---
### Decrement
- `AnyCache#decrement(key, amount = 1, [expires_in:])` - decrement entry's value by the given amount
and set the new expiration time if needed;
- can decrement only nonexistent entries OR entries that were written with `raw: true` option;```ruby
# --- decrement existing entry ---
cache_store.write("data", 15, raw: true) # you must provide :raw => true for decrementable entries# --- decrement by default value (1) ---
cache_store.decrement("data") # => 14# --- decrement by custom value ---
cache_store.decrement("data", 10) # => 4# --- decrement and expire after 5 seconds
cache_store.decrememnt("data", expirs_in: 5) # => 3# --- decrement nonexistent entry (create new entry) ---
cache_store.decrememnt("another_data", 2, expires_in: 10) # => -2 (or 0 for Dalli::Client)# --- read decrementable entry ---
cache_store.read("data", raw: true) # you must provide :raw => true for decrementable entries
```---
### Expire
- `AnyCache#expire(key, [expires_in:])` - expire entry immediately or set the new expiration time;
```ruby
# --- expire immediately ---
cache_store.expire("data")# --- set new expiration time (in seconds) --
cache_store.expire("data", expires_in: 36)
```---
### Persist
- `AnyCache#persist(key)` - change entry's expiration time to permanent;
```ruby
# --- create temporal entry (30 seconds) ---
cache_store.write("data", { a: 1 }, expires_in: 30)# --- remove entry expiration (make it permanent) ---
cache_store.persist("data")
```---
### Existence
- `AnyCache#exist?(key)` - determine if an entry exists;
```ruby
# --- entry exists ---
cache_store.exist?("data") # => true# --- entry does not exist ---
cache_store.exist?("another-data") # => false
```---
### Clear
- `AnyCache#clear()` - clear cache database;
```ruby
# --- prepare cache data ---
cache_store.write("data", { a: 1, b: 2 })
cache_store.write("another_data", 123_456)cache_store.read("data") # => { a: 1, b: 2 }
cache_store.read("another_data") # => 123_456# --- clear cache ---
cache_store.clearcache_store.read("data") # => nil
cache_store.read("another_data") # => nil
```---
### Cleanup
- `AnyCache#cleanup()` - remove expired entries from cache database
(make sense only for `:as_file_store` and `:as_memory_store` cache clients);```ruby
# --- prepare cache data ---
cache_store.write("data", "123", expires_in: 5)
cache_store.write("another_data", "456", expires_in: 10)# --- waiting for cache exiration (10 seconds) ---
cache_store.cleanup # remove expired entries from database (release disk space for example)
```---
## Plugins
`AnyCache` provides a set of plugins and an interface for controllable plugin registering and loading.
```ruby
# --- show names of registered plugins ---
AnyCache.plugins # => array of strings# --- load specific plugin ---
AnyCache.plugin(:plugin_name) # or AnyCache.plugin('plugin_name')
```---
## Build
- see [bin/rspec](bin/rspec)
```shell
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-redis-cache-store # run specs with ActiveSupport::Cache::RedisCacheStore
bin/rspec --test-as-dalli-store # run specs with ActiveSupport::Cache::DalliStore
bin/rspec --test-as-mem-cache-store # run specs with ActiveSupport::Cache::MemCacheStore
bin/rspec --test-as-file-store # run specs with ActiveSupport::Cache::FileStore
bin/rspec --test-as-memory-store # run specs with ActiveSupport::Cache::MemoryStore
```---
## Roadmap
- centralized Logging API (core API);
- instrumentation layer (public API);
- more robust delegation API (core API);
- global and configurable default expiration time (public API);
- `#delete_matched` for memcached-based cache storages (core/public API);
- rails integration (public API);---
## 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](https://github.com/0exp/)