= stockpile
code :: https://github.com/halostatue/stockpile/
bugs :: https://github.com/halostatue/stockpile/issues
continuous integration :: {}[https://travis-ci.org/halostatue/stockpile]
== Description
Stockpile is a simple key-value store connection manager framework. Stockpile
itself does not implement a connection manager, but places expectations for
implemented connection managers. So far, only Redis has been implemented
(stockpile-redis).
Stockpile also provides an adapter so that its functionality can be accessed
from within a module.
Release 1.1 fixes an issue with early initialization of an injected Stockpile
instance during adaptation
({stockpile#2}[https://githbub.com/halostatue/stockpile/issues/2]). Several
small improvements to Stockpile.new, Stockpile#connect, and
Stockpile#connection_for have been documented.
== Features
* Stockpile manages key-value store connections. There are two variants:
* *wide* (Stockpile.new, the default), where additional client
connections are new instances of the client library;
* *narrow* (Stockpile.new(narrow: true)), where additional client
connections use the same client library instance.
* Stockpile can also be injected into a module
(Stockpile.inject!(self, options = {})), which gives the module
cache management and adapter methods (.cache and
.cache_adapter, by default).
== Requirements
The desired key-value store must already be installed and/or specified in
your Gemfile.
== Synopsis
wide = Stockpile.new # A Stockpile instance.
wide.connection.set('hello', 'world') # => 'OK'
wide.connection.get('hello') # => 'world'
# Connections are independent from one another.
wide.connection_for(:other) != wide.connection # => true
# Or set ENV['STOCKPILE_CONNECTION_WDITH'] = 'narrow'
narrow = Stockpile.new(narrow: true) # A 'narrow' Stockpile to Redis.
narrow.connection_for(:other) == narrow.connection # => true
# Special Redis::Namespace handling for Resque. Assumes that redis-namespace
# has been installed, as well.
narrow.connection_for(:resque) != narrow.connection # => true
narrow.connection_for(:resque).redis == narrow.connection # => true
# Standard namespace handling.
narrow.connection_for(:other, namespace: 'other') !=
narrow.connection # => true
narrow.connection_for(:other, namespace: 'other').redis !=
narrow.connection # => true
# Show a Stockpile with no adapter capabilities, but name the method
# stockpile, not cache. This will still usefully manage connections.
module Cacher
Stockpile.inject!(self, method: :stockpile, adaptable: false)
end
Cacher.respond_to?(:stockpile) # => true
Cacher.respond_to?(:stockpile_adapter) # => false
Cacher.stockpile.connection.set('hello', 'world') # => 'OK'
Cacher.stockpile.connection.get('hello') # => 'world'
# Now a Stockpile with adapter capabilities.
module Jobber
module LastRunTime
def last_run_time(key, value = nil)
if value
connection.hset(__method__, key, value.utc.iso8601)
else
value = connection.hget(__method__, key)
Time.parse(value) if value
end
end
end
Stockpile.inject!(self)
end
Jobber.respond_to?(:cache) # => true
Jobber.respond_to?(:cache_adapter) # => true
# Four ways:
# 1. Adapt Jobber.cache to recognize #last_run_time.
Jobber.cache_adapter(Jobber::LastRunTime)
Jobber.cache.last_run_time('hello', t = Time.now) # => true
Jobber.cache.last_run_time('hello') # => approximately t
# 2. Adapt Jobber.cache and another module to recognize #last_run_time.
module Foo; end
Jobber.cache_adapter(Jobber::LastRunTime, Foo)
Foo.last_run_time('hello', t = Time.now) # => true
Foo.last_run_time('hello') # => approximately t
# 3. Adapt Jobber.cache and Jobber to recognize #last_run_time.
Jobber.cache_adapter(Jobber::LastRunTime, Jobber)
Jobber.last_run_time('hello', t = Time.now) # => true
Jobber.last_run_time('hello') # => approximately t
# 4. Adapt Jobber.cache and Jobber::LastRunTime to recognize #last_run_time.
Jobber.cache_adapter!(Jobber::LastRunTime)
# or Jobber.cache_adapter(Jobber::LastRunTime, Jobber::LastRunTime)
Jobber::LastRunTime.last_run_time('hello', t = Time.now) # => true
Jobber::LastRunTime.last_run_time('hello') # => approximately t
== Background
Stockpile is the evolution of concepts I have applied to Rails applications
over the last few years when working with Redis, and avoids the following
common but suboptimal patterns:
* Developers use +REDIS+ or $redis to initialize and access their
Redis instances. This could be fixed by using +Redis.current+, but that still
exposes implementation details unnecessarily.
* Redis methods are often exposed directly in controllers or models, as
render json: $redis.hget('last_run_time', params[:method])
We don’t like seeing direct database access methods in our controllers, so
why do we put up with this for Redis?
* Each Redis client manages its own connections, and at least one client
reconnection is forgotten when using a forking server like Unicorn.
* Some providers of Redis services restrict the number of simultaneous
connections to a given Redis instance. With Rails caching, an application
cache, and Resque there are at least three simultaneous connections to Redis
for a given Rails server instancne, unless the same connection is reused.
=== Sample Rails Application
I will be adapting a sample Rails application to demonstrate how Stockpile can
be used in Rails. A link to it will be provided here when it is complete.
== Install
Stockpile is not intended to be installed by itself, as it does not implement a
key-value store specific connection manager. Instead, install a store-specific
gem which depends on Stockpile.
gem 'stockpile-redis', '~> 1.1'
Or manually install:
% gem install stockpile-redis
and require Stockpile in your code:
require 'stockpile/redis'
== Stockpile Semantic Versioning
Stockpile uses a {Semantic Versioning}[http://semver.org/] scheme with one
change:
* When PATCH is zero (+0+), it will be omitted from version references.
:include: Contributing.rdoc
:include: Licence.rdoc