Skip to content

Commit

Permalink
Merge 210ebc2 into 7e179e0
Browse files Browse the repository at this point in the history
  • Loading branch information
0exp committed Sep 26, 2018
2 parents 7e179e0 + 210ebc2 commit 75587ce
Show file tree
Hide file tree
Showing 21 changed files with 951 additions and 153 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.

## [Unreleased]
### Added
- multi-operations: `#read_multi`, `#write_multi`, `#fetch_multi`, `#delete_matched`;
- logging:
- configuration: `AnyCache.configure { |conf| conf.logger = your_logger_object }`;
- disable logging: `AnyCache.configure { |conf| conf.logger = nil }`;
Expand Down
138 changes: 115 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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 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`.
`fetch`, `read`, `write`, `delete`, `fetch_multi`, `read_multi`, `write_multi`, `delete_matched`, `expire`, `persist`, `exist?`, `clear`, `increment`, `decrement`.

Supported clients:

Expand Down Expand Up @@ -49,12 +49,11 @@ require 'any_cache'
- [Custom cache clients](#custom-cache-clients)
- [Logging](#logging)
- [Operations](#operations)
- [Fetch](#fetch)
- [Read](#read)
- [Write](#write)
- [Delete](#delete)
- [Increment](#increment)
- [Decrement](#decrement)
- [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)
Expand Down Expand Up @@ -118,7 +117,7 @@ storage instantiation works via `.build` method without explicit attributes.
Supported drivers:

- `:redis` - [Redis](#anycache-with-redis);
- `:redis_tore` - [Redis::Client](#anycache-with-redisstore);
- `:redis_store` - [Redis::Client](#anycache-with-redisstore);
- `:dalli` - [Dalli::Client](#anycache-with-dalliclient);
- `:as_redis_cache_store` - [ActiveSupport::Cache::RedisCacheStore](#anycache-with-activesupportcacherediscachestore);
- `:as_mem_cache_store` - [ActiveSupport::Cache::MemCacheStore](#anycache-with-activesupportcachememcachestore);
Expand Down Expand Up @@ -251,10 +250,14 @@ dalli_cache = DalliCache.build

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

- `#fetch(*key, [**options])` ([doc](#fetch))
- `#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))
Expand Down Expand Up @@ -308,8 +311,8 @@ end

Log message format:

```
[AnyCache<CACHER_NAME>/Activity<OPERATION_NAME>]: performed <OPERATION NAME> operation with
```shell
[AnyCache<CACHER_NAME>/Activity<OPERATION_NAME>]: performed <OPERATION_NAME> operation with
params: INSPECTED_ARGUMENTS and options: INSPECTED_OPTIONS
```

Expand All @@ -332,12 +335,11 @@ any_cache.clear

`AnyCache` provides a following operation set:

- [fetch](#fetch)
- [read](#read)
- [write](#write)
- [delete](#delete)
- [increment](#increment)
- [decrement](#decrement)
- [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)
Expand All @@ -347,11 +349,11 @@ any_cache.clear

### Fetch

- `AnyCache#fetch(key, [force:], [expires_in:], [&block])`
- `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 block has been passed and data with the given key does not exist - that block
will be called and the return value will be written to the cache;
- 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;

```ruby
# --- entry exists ---
Expand All @@ -360,26 +362,64 @@ cache_store.fetch("data") { "new_data" } # => "some_data"

# --- entry does not exist ---
cache_store.fetch("data") # => nil
cache_store.fetch("data") { "new_data" } # => "new_data"
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) { "new_data" } # => "new_data"
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) { "new_data" } # => "new_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;

```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
Expand All @@ -394,6 +434,25 @@ cache_store.read("data") # => nil

---

### Read Multi

- `AnyCache#read_multi(*keys)`
- get entries from the cache storage in hash form;
- nonexistent entries will be fetched with `nil` values;

```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
}
```

---

### Write

- `AnyCache#write(key, value, [expires_in:])` - write a new entry to the cache storage
Expand All @@ -408,6 +467,16 @@ cache_store.write("data", 123, expires_in: 60)

---

### Write Multi

- `AnyCache#write_multi(**entries)` - write a set of permanent entries to the cache storage

```ruby
cache_store.write_multi("data" => "test", "another_data" => 123)
```

---

### Delete

- `AnyCache#delete(key)` - remove entry from the cache storage
Expand All @@ -418,6 +487,21 @@ cache_store.delete("data")

---

### Delete Matched

- `AnyCache#delete_matched(pattern)` - delete all entries with keys matching the pattern
- currently unsupported: `:dalli`, `:as_mem_cache_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
Expand Down Expand Up @@ -545,6 +629,14 @@ bin/rspec --test-as-mem-cache-store # run specs with ActiveSupport::Cache::MemCa

---

## Roadmap

- instrumentation layer;
- global and configurable default expiration time;
- `#delete_matched` for memcached-based cache storages;

---

## Contributing

- Fork it (https://github.com/0exp/any_cache/fork)
Expand Down
8 changes: 6 additions & 2 deletions lib/any_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,19 @@ def build(driver = Drivers.build(config))
# @since 0.3.0
def_loggable_delegators :adapter,
:read,
:read_multi,
:write,
:write_multi,
:fetch,
:fetch_multi,
:delete,
:delete_matched,
:increment,
:decrement,
:expire,
:persist,
:clear,
:exist?,
:fetch
:exist?

# @return [AnyCache::Adapters::Basic]
#
Expand Down
87 changes: 71 additions & 16 deletions lib/any_cache/adapters/active_support_mem_cache_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ def supported_driver?(driver)
end
end

# @return [Array]
#
# @api private
# @since 0.3.0
READ_MULTI_EMPTY_KEYS_SET = [].freeze

# @return [NilClass]
#
# @api private
Expand Down Expand Up @@ -44,7 +50,7 @@ def supported_driver?(driver)
# @since 0.2.0
def_delegators :driver, :delete, :clear

# @param key
# @param key [String]
# @param options [Hash]
# @return [Object]
#
Expand All @@ -56,6 +62,20 @@ def read(key, **options)
driver.read(key, raw: raw)
end

# @param keys [Array<String>]
# @param options [Hash]
# @return [Hash]
#
# @api private
# @since 0.3.0
def read_multi(*keys, **options)
raw = options.fetch(:raw, true)

driver.read_multi(*keys, raw: raw).tap do |res|
res.merge!(Hash[(keys - res.keys).zip(READ_MULTI_EMPTY_KEYS_SET)])
end
end

# @param key [String]
# @param value [Object]
# @option expires_in [NilClass, Integer] Time in seconds
Expand All @@ -70,6 +90,56 @@ def write(key, value, **options)
driver.write(key, value, expires_in: expires_in, raw: raw)
end

# @param entries [Hash]
# @param options [Hash]
# @return [void]
#
# @api private
# @since 0.3.0
def write_multi(entries, **options)
raw = options.fetch(:raw, true)

driver.write_multi(entries, expires_in: NO_EXPIRATION_TTL, raw: raw)
end

# @param key [String]
# @option expires_in [Integer]
# @option force [Boolean, Proc]
# @return [Object]
#
# @api private
# @since 0.2.0
def fetch(key, **options, &fallback)
force_rewrite = options.fetch(:force, false)
force_rewrite = force_rewrite.call(key) if force_rewrite.respond_to?(:call)
expires_in = options.fetch(:expires_in, NO_EXPIRATION_TTL)

driver.fetch(key, force: force_rewrite, expires_in: expires_in, &fallback)
end

# @param keys [Array<String>]
# @param options [Hash]
# @param fallback [Proc]
# @return [Hash]
#
# @api private
# @since 0.3.0
def fetch_multi(*keys, **options, &fallback)
keys.each_with_object({}) do |key, dataset|
dataset[key] = fetch(key, **options, &fallback)
end
end

# @param pattern [String, Regexp]
# @param options [Hash]
# @return [void]
#
# @api private
# @since 0.3.0
def delete_matched(pattern, **options)
# TODO: implement
end

# @param key [String]
# @param amount [Integer]
# @options expires_in [Integer]
Expand Down Expand Up @@ -144,20 +214,5 @@ def persist(key, **options)
def exist?(key, **options)
driver.exist?(key)
end

# @param key [String]
# @option expires_in [Integer]
# @option force [Boolean]
# @return [Object]
#
# @api private
# @since 0.2.0
def fetch(key, **options, &block)
force_rewrite = options.fetch(:force, false)
force_rewrite = force_rewrite.call if force_rewrite.respond_to?(:call)
expires_in = options.fetch(:expires_in, NO_EXPIRATION_TTL)

driver.fetch(key, force: force_rewrite, expires_in: expires_in, &block)
end
end
end
Loading

0 comments on commit 75587ce

Please sign in to comment.