stockpile 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d12f955c01ed9d46dd13ec2fc357cbb086094146
4
+ data.tar.gz: 5612ead9571a141b3b68695727655c1e03d680e0
5
+ SHA512:
6
+ metadata.gz: a3734ac43b06cfe7fe5d3fdbdc60cbd9f7560dfdce10fdee63d1d8128291de176baa307b368ef39fc02fe9aa307fde5a552262f50455fb02ab4058ebd8c2a2a9
7
+ data.tar.gz: 99989e77edc96cc5f193b1c327cf42c4c573b43250f236ffe018cbf4f23240cbc5c327065ae063eec4e99e9b090b8a3c188cea37c07dac8138a25f71a34aa221
@@ -0,0 +1,27 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ # .minitest.rb ensures that the gem version of minitest is used.
7
+ at.testlib = ".minitest.rb"
8
+ # at.testlib = "minitest/unit"
9
+ #
10
+ # at.extra_files << "../some/external/dependency.rb"
11
+ #
12
+ # at.libs << ":../some/external"
13
+ #
14
+ # at.add_exception "vendor"
15
+ #
16
+ # at.add_mapping(/dependency.rb/) do |f, _|
17
+ # at.files_matching(/test_.*rb$/)
18
+ # end
19
+ #
20
+ # %w(TestA TestB).each do |klass|
21
+ # at.extra_class_map[klass] = "test/test_misc.rb"
22
+ # end
23
+ end
24
+
25
+ # Autotest.add_hook :run_command do |at|
26
+ # system "rake build"
27
+ # end
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,2 @@
1
+ gem "minitest"
2
+ require "minitest/autorun"
@@ -0,0 +1,34 @@
1
+ ---
2
+ language: ruby
3
+ rvm:
4
+ - 2.1.0
5
+ - 2.0.0
6
+ - 1.9.3
7
+ - ruby-head
8
+ - jruby-19mode
9
+ - jruby-head
10
+ - rbx-2
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: rbx-2
14
+ - rvm: jruby-head
15
+ - rvm: ruby-head
16
+ gemfile:
17
+ - Gemfile
18
+ before_script:
19
+ - |
20
+ case "${TRAVIS_RUBY_VERSION}" in
21
+ rbx*)
22
+ gem install psych
23
+ ;;
24
+ esac
25
+ - rake travis:before -t
26
+ script: rake travis
27
+ after_script:
28
+ - rake travis:after -t
29
+ notifications:
30
+ email:
31
+ recipients:
32
+ - FIX@example.com
33
+ on_success: change
34
+ on_failure: always
@@ -0,0 +1,65 @@
1
+ == Contributing
2
+
3
+ I value any contribution to stockpile you can provide: a bug report, a
4
+ feature request, or code contributions.
5
+
6
+ As stockpile is a complex codebase, there are a few guidelines:
7
+
8
+ * Changes *will* *not* be accepted without tests. The test suite is written
9
+ with {Minitest}[https://github.com/seattlerb/minitest].
10
+ * Match my coding style.
11
+ * Use a thoughtfully-named topic branch that contains your change. Rebase your
12
+ commits into logical chunks as necessary.
13
+ * Use {quality commit messages}[http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html].
14
+ * Do not change the version number; when your patch is accepted and a release
15
+ is made, the version will be updated at that point.
16
+ * Submit a GitHub pull request with your changes.
17
+ * New behaviours require new or updated documentation.
18
+
19
+ === Test Dependencies
20
+
21
+ stockpile uses Ryan Davis’s {Hoe}[https://github.com/seattlerb/hoe]
22
+ to manage the release process, and it adds a number of rake tasks. You will
23
+ mostly be interested in:
24
+
25
+ $ rake
26
+
27
+ which runs the tests the same way that:
28
+
29
+ $ rake test
30
+ $ rake travis
31
+
32
+ will do.
33
+
34
+ To assist with the installation of the development dependencies for
35
+ stockpile, I have provided the simplest possible Gemfile pointing to
36
+ the (generated) +stockpile.gemspec+ file. This will permit you to do:
37
+
38
+ $ bundle install
39
+
40
+ to get the development dependencies. If you aleady have +hoe+ installed, you
41
+ can accomplish the same thing with:
42
+
43
+ $ rake newb
44
+
45
+ This task will install any missing dependencies, run the tests/specs, and
46
+ generate the RDoc.
47
+
48
+ === Workflow
49
+
50
+ Here's the most direct way to get your work merged into the project:
51
+
52
+ * Fork the project.
53
+ * Clone down your fork (<tt>git clone git://github.com/halostatue/stockpile.git</tt>).
54
+ * Create a topic branch to contain your change (<tt>git checkout -b my\_awesome\_feature</tt>).
55
+ * Hack away, add tests. Not necessarily in that order.
56
+ * Make sure everything still passes by running +rake+.
57
+ * If necessary, rebase your commits into logical chunks, without errors.
58
+ * Push the branch up (<tt>git push origin my\_awesome\_feature</tt>).
59
+ * Create a pull request against halostatue/stockpile and describe
60
+ what your change does and the why you think it should be merged.
61
+
62
+ === Contributors
63
+
64
+ * Austin Ziegler created stockpile.
65
+ * Sean Miller helped wordsmith the documentation. Any errors are mine, not his.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+
3
+ # NOTE: This file is present to keep Travis CI happy. Edits to it will not
4
+ # be accepted.
5
+
6
+ source "https://rubygems.org/"
7
+ gemspec
8
+
9
+ # vim: syntax=ruby
@@ -0,0 +1,5 @@
1
+ === 1.0 / 2015-01-21
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
@@ -0,0 +1,27 @@
1
+ == Licence
2
+
3
+ This software is available under an MIT-style licence.
4
+
5
+ * Copyright 2015 Austin Ziegler
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ this software and associated documentation files (the "Software"), to deal in
9
+ the Software without restriction, including without limitation the rights to
10
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
+ of the Software, and to permit persons to whom the Software is furnished to do
12
+ so, subject to the following conditions:
13
+
14
+ * The names of its contributors may not be used to endorse or promote
15
+ products derived from this software without specific prior written
16
+ permission.
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ .autotest
2
+ .gemtest
3
+ .minitest.rb
4
+ .travis.yml
5
+ Contributing.rdoc
6
+ Gemfile
7
+ History.rdoc
8
+ Licence.rdoc
9
+ Manifest.txt
10
+ README.rdoc
11
+ Rakefile
12
+ lib/stockpile.rb
13
+ test/minitest_config.rb
14
+ test/test_stockpile.rb
@@ -0,0 +1,157 @@
1
+ = stockpile
2
+
3
+ code :: https://github.com/halostatue/stockpile/
4
+ bugs :: https://github.com/halostatue/stockpile/issues
5
+ continuous integration :: {<img src="https://travis-ci.org/halostatue/stockpile.png" />}[https://travis-ci.org/halostatue/stockpile]
6
+
7
+ == Description
8
+
9
+ Stockpile is a simple key-value store connection manager framework. Stockpile
10
+ itself does not implement a connection manager, but places expectations for
11
+ implemented connection managers. So far, only Redis has been implemented
12
+ (stockpile-redis).
13
+
14
+ Stockpile also provides an adapter so that its functionality can be accessed
15
+ from within a module.
16
+
17
+ == Features
18
+
19
+ * Stockpile manages key-value store connections. There are two variants:
20
+
21
+ * *wide* (<tt>Stockpile.new</tt>, the default), where additional client
22
+ connections are new instances of the client library;
23
+ * *narrow* (<tt>Stockpile.new(narrow: true)</tt>), where additional client
24
+ connections use the same client library instance.
25
+
26
+ * Stockpile can also be injected into a module
27
+ (<tt>Stockpile.inject!(self, options = {})</tt>), which gives the module
28
+ cache management and adapter methods (<tt>.cache</tt> and
29
+ <tt>.cache_adapter</tt>, by default).
30
+
31
+ == Requirements
32
+
33
+ The desired key-value store must already be installed and/or specified in
34
+ your Gemfile.
35
+
36
+ == Synopsis
37
+
38
+ wide = Stockpile.new # A Stockpile to Redis.
39
+ wide.connection.set('hello', 'world') # => 'OK'
40
+ wide.connection.get('hello') # => 'world'
41
+
42
+ # Connections are independent from one another.
43
+ wide.connection_for(:other) != wide.connection # => true
44
+
45
+ # Or set ENV['STOCKPILE_CONNECTION_WDITH'] = 'narrow'
46
+ narrow = Stockpile.new(narrow: true) # A 'narrow' Stockpile to Redis.
47
+ narrow.connection_for(:other) == narrow.connection # => true
48
+
49
+ # Special Redis::Namespace handling for Resque. Assumes that redis-namespace
50
+ # has been installed, as well.
51
+ narrow.connection_for(:resque) != narrow.connection # => true
52
+ narrow.connection_for(:resque).redis == narrow.connection # => true
53
+
54
+ # Show a Stockpile with no adapter capabilities, but name the method
55
+ # stockpile, not cache. This will still usefully manage connections.
56
+ module Cacher
57
+ Stockpile.inject!(self, method: :stockpile, adaptable: false)
58
+ end
59
+ Cacher.respond_to?(:stockpile) # => true
60
+ Cacher.respond_to?(:stockpile_adapter) # => false
61
+ Cacher.stockpile.connection.set('hello', 'world') # => 'OK'
62
+ Cacher.stockpile.connection.get('hello') # => 'world'
63
+
64
+ # Now a Stockpile with adapter capabilities.
65
+ module Jobber
66
+ module LastRunTime
67
+ def last_run_time(key, value = nil)
68
+ if value
69
+ connection.hset(__method__, key, value.utc.iso8601)
70
+ else
71
+ value = connection.hget(__method__, key)
72
+ Time.parse(value) if value
73
+ end
74
+ end
75
+ end
76
+
77
+ Stockpile.inject!(self)
78
+ end
79
+ Jobber.respond_to?(:cache) # => true
80
+ Jobber.respond_to?(:cache_adapter) # => true
81
+
82
+ # Four ways:
83
+ # 1. Adapt Jobber.cache to recognize #last_run_time.
84
+ Jobber.cache_adapter(Jobber::LastRunTime)
85
+ Jobber.cache.last_run_time('hello', t = Time.now) # => true
86
+ Jobber.cache.last_run_time('hello') # => approximately t
87
+
88
+ # 2. Adapt Jobber.cache and another module to recognize #last_run_time.
89
+ module Foo; end
90
+ Jobber.cache_adapter(Jobber::LastRunTime, Foo)
91
+ Foo.last_run_time('hello', t = Time.now) # => true
92
+ Foo.last_run_time('hello') # => approximately t
93
+
94
+ # 3. Adapt Jobber.cache and Jobber to recognize #last_run_time.
95
+ Jobber.cache_adapter(Jobber::LastRunTime, Jobber)
96
+ Jobber.last_run_time('hello', t = Time.now) # => true
97
+ Jobber.last_run_time('hello') # => approximately t
98
+
99
+ # 4. Adapt Jobber.cache and Jobber::LastRunTime to recognize #last_run_time.
100
+ Jobber.cache_adapter!(Jobber::LastRunTime)
101
+ # or Jobber.cache_adapter(Jobber::LastRunTime, Jobber::LastRunTime)
102
+ Jobber::LastRunTime.last_run_time('hello', t = Time.now) # => true
103
+ Jobber::LastRunTime.last_run_time('hello') # => approximately t
104
+
105
+ == Background
106
+
107
+ Stockpile is the evolution of concepts I have applied to Rails applications
108
+ over the last few years when working with Redis, and avoids the following
109
+ common but suboptimal patterns:
110
+
111
+ * Developers use +REDIS+ or <tt>$redis</tt> to initialize and access their
112
+ Redis instances. This could be fixed by using +Redis.current+, but that still
113
+ exposes implementation details unnecessarily.
114
+ * Redis methods are often exposed directly in controllers or models, as
115
+
116
+ render json: $redis.hget('last_run_time', params[:method])
117
+
118
+ We don’t like seeing direct database access methods in our controllers, so
119
+ why do we put up with this for Redis?
120
+ * Each Redis client manages its own connections, and at least one client
121
+ reconnection is forgotten when using a forking server like Unicorn.
122
+ * Some providers of Redis services restrict the number of simultaneous
123
+ connections to a given Redis instance. With Rails caching, an application
124
+ cache, and Resque there are at least three simultaneous connections to Redis
125
+ for a given Rails server instancne, unless the same connection is reused.
126
+
127
+ === Sample Rails Application
128
+
129
+ I will be adapting a sample Rails application to demonstrate how Stockpile can
130
+ be used in Rails. A link to it will be provided here when it is complete.
131
+
132
+ == Install
133
+
134
+ Stockpile is not intended to be installed by itself, as it does not implement a
135
+ key-value store specific connection manager. Instead, install an
136
+ store-specific gem which depends on Stockpile.
137
+
138
+ gem 'stockpile-redis', '~> 1.0'
139
+
140
+ Or manually install:
141
+
142
+ % gem install stockpile-redis
143
+
144
+ and require Stockpile in your code:
145
+
146
+ require 'stockpile/redis'
147
+
148
+ == Stockpile Semantic Versioning
149
+
150
+ Stockpile uses a {Semantic Versioning}[http://semver.org/] scheme with one
151
+ change:
152
+
153
+ * When PATCH is zero (+0+), it will be omitted from version references.
154
+
155
+ :include: Contributing.rdoc
156
+
157
+ :include: Licence.rdoc
@@ -0,0 +1,51 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :doofus
7
+ Hoe.plugin :gemspec2
8
+ Hoe.plugin :git
9
+ Hoe.plugin :minitest
10
+ Hoe.plugin :travis
11
+ Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS']
12
+
13
+ spec = Hoe.spec 'stockpile' do
14
+ developer('Austin Ziegler', 'halostatue@gmail.com')
15
+
16
+ self.history_file = 'History.rdoc'
17
+ self.readme_file = 'README.rdoc'
18
+ self.extra_rdoc_files = FileList["*.rdoc"].to_a
19
+
20
+ license "MIT"
21
+
22
+ self.need_tar = true
23
+ self.require_ruby_version '>= 1.9.2'
24
+
25
+ self.extra_dev_deps << ['hoe-doofus', '~> 1.0']
26
+ self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
27
+ self.extra_dev_deps << ['hoe-git', '~> 1.5']
28
+ self.extra_dev_deps << ['hoe-travis', '~> 1.2']
29
+ self.extra_dev_deps << ['minitest', '~> 5.4']
30
+ self.extra_dev_deps << ['minitest-around', '~> 0.3']
31
+ self.extra_dev_deps << ['minitest-autotest', '~> 1.0']
32
+ self.extra_dev_deps << ['minitest-bisect', '~> 1.2']
33
+ self.extra_dev_deps << ['minitest-focus', '~> 1.1']
34
+ self.extra_dev_deps << ['minitest-moar', '~> 0.0']
35
+ self.extra_dev_deps << ['minitest-pretty_diff', '~> 0.1']
36
+ self.extra_dev_deps << ['rake', '>= 10.0']
37
+ self.extra_dev_deps << ['simplecov', '~> 0.7']
38
+ end
39
+
40
+ namespace :test do
41
+ task :coverage do
42
+ spec.test_prelude = [
43
+ 'require "simplecov"',
44
+ 'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
45
+ 'gem "minitest"'
46
+ ].join('; ')
47
+ Rake::Task['test'].execute
48
+ end
49
+ end
50
+
51
+ # vim: syntax=ruby
@@ -0,0 +1,220 @@
1
+ # coding: utf-8
2
+
3
+ require 'forwardable'
4
+
5
+ # Stockpile is a thin wrapper around connections to a fast key-value store used
6
+ # for caching (currently only supporting Redis).
7
+ #
8
+ # This provides a couple of layers of functionality:
9
+ #
10
+ # * Connection management. Some third-party providers of Redis limit
11
+ # simultaneous connections; Stockpile can manage a single connection that
12
+ # gets shared by all clients using a Stockpile instance.
13
+ # * Providing an application-level cache adapter mechanism.
14
+ class Stockpile
15
+ extend Forwardable
16
+
17
+ VERSION = "1.0" # :nodoc:
18
+
19
+ @default_manager = nil
20
+
21
+ class << self
22
+ # Determines if the default connection width is narrow or wide based on
23
+ # the environment variable STOCKPILE_CONNECTION_WIDTH.
24
+ def narrow?
25
+ (ENV['STOCKPILE_CONNECTION_WIDTH'] == 'narrow')
26
+ end
27
+
28
+ # Enables module or class +mod+ to contain a Stockpile instance and
29
+ # provide an adapter interface (this can be disabled). This creates a
30
+ # singleton method that returns a singleton Stockpile instance.
31
+ #
32
+ # === Options
33
+ # +method+:: The name of the method that manages the Stockpile instance.
34
+ # Defaults to +cache+.
35
+ # +adaptable+:: Defines an adapter method if truthy (the default). Pass a
36
+ # falsy value to disable. (The created adapter method will be
37
+ # named according to the value of +method+, and so defaults
38
+ # to +cache_adapter+.
39
+ #
40
+ # === Synopsis
41
+ #
42
+ # # Using only for connection management.
43
+ # module Application
44
+ # Stockpile.inject!(self, adaptable: false)
45
+ # end
46
+ # Application.cache # => a stockpile instance
47
+ # Application.cache.connection.set('answer', 42)
48
+ # Application.cache.connection.get('answer')
49
+ #
50
+ # module LastRunTime
51
+ # def last_run_time(key, value = nil)
52
+ # if value
53
+ # connection.hset(__method__, key, value.utc.iso8601)
54
+ # else
55
+ # value = connection.hget(__method__, key)
56
+ # Time.parse(value) if value
57
+ # end
58
+ # end
59
+ # end
60
+ #
61
+ # module AdaptableApplication; end
62
+ # Stockpile.inject!(AdaptableApplication)
63
+ #
64
+ # # Adapt the cache object to recognize #last_run_time;
65
+ # AdaptableApplication.cache_adapter(LastRunTime)
66
+ # AdaptableApplication.cache.last_run_time('adaptable_application')
67
+ #
68
+ # # or adapt AdaptableApplication to recognize #last_run_time;
69
+ # AdaptableApplication.cache_adapter(LastRunTime,
70
+ # AdaptableApplication)
71
+ # AdaptableApplication.last_run_time('adaptable_application')
72
+ #
73
+ # # or adapt LastRunTime to recognize #last_run_time.
74
+ # AdaptableApplication.cache_adapter!(LastRunTime)
75
+ # LastRunTime.last_run_time('adaptable_application')
76
+ def inject!(mod, options = {})
77
+ unless mod.kind_of?(Module)
78
+ raise ArgumentError, "#{mod} is not a class or module"
79
+ end
80
+
81
+ name = options.fetch(:method, :cache).to_sym
82
+ msc = mod.singleton_class
83
+
84
+ default = options.fetch(:default_manager, nil)
85
+
86
+ msc.send(:define_method, name) do |init_options = {}|
87
+ init_options = init_options.merge(default_manager: default)
88
+ @__stockpile__ ||= ::Stockpile.new(init_options)
89
+ end
90
+
91
+ if options.fetch(:adaptable, true)
92
+ adapter = :"#{name}_adapter"
93
+ msc.send(:define_method, adapter) do |m, k = nil|
94
+ o = self
95
+ send(name).singleton_class.send(:include, m)
96
+
97
+ if k
98
+ mk = k.singleton_class
99
+ m.public_instance_methods.each do |pim|
100
+ mk.send(:define_method, pim) do |*args, &block|
101
+ o.send(name).send(pim, *args, &block)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ msc.send(:define_method, :"#{adapter}!") do |m|
108
+ send(adapter, m, m)
109
+ end
110
+ end
111
+ end
112
+
113
+ # The default Stockpile cache connection manager.
114
+ attr_accessor :default_manager
115
+ end
116
+
117
+ # Creates a new Stockpile instance and connects to the connection provider
118
+ # using the provided options and block.
119
+ #
120
+ # === Options
121
+ #
122
+ # The options hash contains configuration for the Stockpile and its
123
+ # connection manager. The following options are handled specially by the
124
+ # Stockpile constructor and not made available to the connection provider
125
+ # constructor.
126
+ #
127
+ # +manager+:: The connection manager that will be used for creating
128
+ # connections to this Stockpile. If not provided, either
129
+ # +default_manager+ or ::Stockpile.default_manager will be used.
130
+ # An error will be raised if no connection provider is available
131
+ # through any means.
132
+ # +clients+:: Connections will be created for the provided list of clients.
133
+ # These connections must be assigned to their appropriate clients
134
+ # after initialization. This may also be called +client+.
135
+ #
136
+ # All other options will be passed to the connection provider.
137
+ #
138
+ # === Synopsis
139
+ #
140
+ # # Create and assign a connection to Redis.current, Resque, and Rollout.
141
+ # # Under a narrow connection management width, all three will be the
142
+ # # same client connection.
143
+ # Stockpile.new(manager: Stockpile::Redis, clients: [ :redis, :resque ]) do |stockpile|
144
+ # Redis.current = stockpile.connection_for(:redis)
145
+ # Resque.redis = stockpile.connection_for(:resque)
146
+ # # Clients will be created by name if necessary.
147
+ # $rollout = Rollout.new(stockpile.connection_for(:rollout))
148
+ # end
149
+ def initialize(options = {})
150
+ manager = options.fetch(:manager) {
151
+ options[:default_manager] || self.class.default_manager
152
+ }
153
+
154
+ unless manager
155
+ raise ArgumentError, "No connection manager provided or set as default."
156
+ end
157
+
158
+ clients = (Array(options[:clients]) + Array(options[:client])).flatten.uniq
159
+
160
+ options = { narrow: Stockpile.narrow? }.merge(options.reject { |k, _|
161
+ k == :manager || k == :clients || k == :client
162
+ })
163
+
164
+ @manager = manager.new(options)
165
+ connect(*clients)
166
+ yield self if block_given?
167
+ end
168
+
169
+ ##
170
+ # :attr_reader: connection
171
+ #
172
+ # The currently active connection to the cache provider.
173
+ def_delegator :@manager, :connection
174
+
175
+ ##
176
+ # :method: connect
177
+ # :call-seq:
178
+ # connect
179
+ # connect(*clients)
180
+ #
181
+ # This will connect the Stockpile instance to the cache provider, optionally
182
+ # including for a set of named clients.
183
+ #
184
+ # If the connection is using a narrow connection width, the same connection
185
+ # will be shared.
186
+ def_delegator :@manager, :connect
187
+
188
+ ##
189
+ # :method: connection_for
190
+ # :call-seq:
191
+ # connection_for(client_name)
192
+ #
193
+ # Returns a connection for a particular client. If the connection manager is
194
+ # using a narrow connection width, this returns the same as #connection.
195
+ #
196
+ # The +client_name+ of +:all+ will always return +nil+.
197
+ #
198
+ # If the requested client does not yet exist, the connection will be created.
199
+ def_delegator :@manager, :connection_for
200
+
201
+ ##
202
+ # :method: reconnect
203
+ # :call-seq:
204
+ # reconnect
205
+ # reconnect(:all)
206
+ # reconnect(*clients)
207
+ #
208
+ # This will reconnect one or more clients.
209
+ def_delegator :@manager, :reconnect
210
+
211
+ ##
212
+ # :method: disconnect
213
+ # :call-seq:
214
+ # disconnect
215
+ # disconnect(:all)
216
+ # disconnect(*clients)
217
+ #
218
+ # This will disconnect one or more clients.
219
+ def_delegator :@manager, :disconnect
220
+ end
@@ -0,0 +1,163 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ gem 'minitest'
4
+ require 'minitest/autorun'
5
+ require 'minitest/pretty_diff'
6
+ require 'minitest/focus'
7
+ require 'minitest/moar'
8
+ require 'minitest/bisect'
9
+
10
+ require 'stockpile'
11
+
12
+ class StockpileTestManager
13
+ class Connection
14
+ class << self
15
+ attr_reader :data
16
+
17
+ def reset_data
18
+ @data = Hash.new { |h, k| h[k] = {} }
19
+ end
20
+ end
21
+
22
+ reset_data
23
+
24
+ def initialize
25
+ @connected = false
26
+ connect
27
+ end
28
+
29
+ def connect
30
+ @connected = true
31
+ end
32
+ alias_method :reconnect, :connect
33
+
34
+ def disconnect
35
+ @connected = false
36
+ end
37
+
38
+ def connected?
39
+ !!@connected
40
+ end
41
+
42
+ def get(key)
43
+ raise unless connected?
44
+ self.class.data[key]
45
+ end
46
+
47
+ def set(key, value)
48
+ raise unless connected?
49
+ self.class.data[key] = value
50
+ end
51
+
52
+ def hget(key, field)
53
+ raise unless connected?
54
+ self.class.data[key][field]
55
+ end
56
+
57
+ def hset(key, field, value)
58
+ raise unless connected?
59
+ self.class.data[key][field] = value
60
+ end
61
+ end
62
+
63
+ def initialize(options = {})
64
+ @narrow = !!options.fetch(:narrow, ::Stockpile.narrow?)
65
+ @connection = nil
66
+ @clients = {}
67
+ end
68
+
69
+ attr_reader :connection
70
+
71
+ def narrow?
72
+ @narrow
73
+ end
74
+
75
+ def connect(*client_names)
76
+ @connection ||= connect_for_any
77
+
78
+ clients_from(*client_names).each { |client_name|
79
+ connection_for(client_name)
80
+ }
81
+
82
+ connection
83
+ end
84
+
85
+ def connection_for(client_name)
86
+ connect unless connection
87
+ return nil if client_name == :all
88
+ @clients[client_name] ||= connect_for_any
89
+ end
90
+
91
+ def reconnect(*client_names)
92
+ return unless connection
93
+
94
+ connection.reconnect
95
+
96
+ unless narrow?
97
+ clients_from(*client_names).each { |client_name|
98
+ client = @clients[client_name]
99
+ client.reconnect if client
100
+ }
101
+ end
102
+
103
+ connection
104
+ end
105
+
106
+ def disconnect(*client_names)
107
+ return unless connection
108
+
109
+ unless narrow?
110
+ clients_from(*client_names).each { |client_name|
111
+ client = @clients[client_name]
112
+ client.disconnect if client
113
+ }
114
+ end
115
+
116
+ connection.disconnect
117
+ end
118
+
119
+ private
120
+ def clients_from(*client_names)
121
+ if client_names.size == 1
122
+ if client_names.first == :all
123
+ @clients.keys
124
+ else
125
+ client_names
126
+ end
127
+ else
128
+ client_names
129
+ end
130
+ end
131
+
132
+ def connect_for_any
133
+ return connection if connection && narrow?
134
+ Connection.new
135
+ end
136
+ end
137
+
138
+ module Minitest::ENVStub
139
+ def setup
140
+ super
141
+ StockpileTestManager::Connection.reset_data
142
+ end
143
+
144
+ def stub_env env, options = {}, *block_args, &block
145
+ mock = lambda { |key|
146
+ env.fetch(key) { |k|
147
+ if options[:passthrough]
148
+ ENV.send(:"__minitest_stub__[]", k)
149
+ else
150
+ nil
151
+ end
152
+ }
153
+ }
154
+
155
+ if defined? Minitest::Moar::Stubbing
156
+ stub ENV, :[], mock, *block_args, &block
157
+ else
158
+ ENV.stub :[], mock, *block_args, &block
159
+ end
160
+ end
161
+
162
+ Minitest::Test.send(:include, self)
163
+ end
@@ -0,0 +1,133 @@
1
+ require 'minitest_config'
2
+ require 'stockpile'
3
+ require 'time'
4
+
5
+ describe Stockpile do
6
+ describe ".narrow?" do
7
+ it "is wide by default" do
8
+ stub_env({}) do
9
+ refute Stockpile.narrow?
10
+ end
11
+ end
12
+
13
+ it "is narrow when $STOCKPILE_CONNECTION_WIDTH = 'narrow'" do
14
+ stub_env({ 'STOCKPILE_CONNECTION_WIDTH' => 'narrow' }) do
15
+ assert Stockpile.narrow?
16
+ end
17
+ end
18
+ end
19
+
20
+ describe ".inject!" do
21
+ let(:mod) { Module.new }
22
+ let(:lrt) {
23
+ Module.new do
24
+ def last_run_time(key, value = nil)
25
+ if value
26
+ connection.hset(__method__, key, value.utc.iso8601)
27
+ else
28
+ value = connection.hget(__method__, key)
29
+ Time.parse(value) if value
30
+ end
31
+ end
32
+ end
33
+ }
34
+
35
+ describe "Stockpile.inject!(Mod):" do
36
+ before { ::Stockpile.inject!(mod) }
37
+
38
+ it "defines Mod.cache" do
39
+ assert_respond_to mod, :cache
40
+ end
41
+
42
+ it "defines Mod.cache_adapter" do
43
+ assert_respond_to mod, :cache_adapter
44
+ end
45
+
46
+ it "defines Mod.cache_adapter!" do
47
+ assert_respond_to mod, :cache_adapter!
48
+ end
49
+
50
+ it "Fails cache initialization" do
51
+ assert_raises ArgumentError do
52
+ mod.cache
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "Stockpile.inject!(Mod, adaptable: false)" do
58
+ before { ::Stockpile.inject!(mod, adaptable: false) }
59
+
60
+ it "defines Mod.cache" do
61
+ assert_respond_to mod, :cache
62
+ end
63
+
64
+ it "does not define Mod.cache_adapter or Mod.cache_adapter!" do
65
+ refute_respond_to mod, :cache_adapter
66
+ refute_respond_to mod, :cache_adapter!
67
+ end
68
+ end
69
+
70
+ describe "Stockpile.inject!(Mod, method: stockpile)" do
71
+ before { ::Stockpile.inject!(mod, method: :stockpile) }
72
+
73
+ it "defines Mod.stockpile" do
74
+ assert_respond_to mod, :stockpile
75
+ end
76
+
77
+ it "defines Mod.stockpile_adapter" do
78
+ assert_respond_to mod, :stockpile_adapter
79
+ end
80
+
81
+ it "defines Mod.stockpile_adapter!" do
82
+ assert_respond_to mod, :stockpile_adapter!
83
+ end
84
+ end
85
+
86
+ describe "Stockpile.inject!(Mod, method: :stockpile, adaptable: false)" do
87
+ before { ::Stockpile.inject!(mod, method: :stockpile, adaptable: false) }
88
+
89
+ it "defines Mod.stockpile" do
90
+ assert_respond_to mod, :stockpile
91
+ end
92
+
93
+ it "does not define Mod.stockpile_adapter or Mod.stockpile_adapter!" do
94
+ refute_respond_to mod, :stockpile_adapter
95
+ refute_respond_to mod, :stockpile_adapter!
96
+ end
97
+ end
98
+
99
+ describe "Mod.cache_adapter" do
100
+ let(:now) { Time.now }
101
+ let(:iso) { now.utc.iso8601 }
102
+ before do
103
+ ::Stockpile.inject!(mod, adaptable: true,
104
+ default_manager: StockpileTestManager)
105
+ end
106
+
107
+ it "adapts the cache with last_run_time" do
108
+ mod.cache_adapter(lrt)
109
+ assert_nil mod.cache.last_run_time('foo')
110
+ assert_equal iso, mod.cache.last_run_time('foo', now)
111
+ assert_equal now.to_i, mod.cache.last_run_time('foo').to_i
112
+ end
113
+
114
+ it "adapts the module with last_run_time" do
115
+ mod.cache_adapter(lrt, mod)
116
+ assert_nil mod.last_run_time('foo')
117
+ assert_equal iso, mod.last_run_time('foo', now)
118
+ assert_equal now.to_i, mod.last_run_time('foo').to_i
119
+ end
120
+
121
+ it "adapts the lrt module with last_run_time" do
122
+ mod.cache_adapter!(lrt)
123
+ assert_nil lrt.last_run_time('foo')
124
+ assert_equal iso, lrt.last_run_time('foo', now)
125
+ assert_equal now.to_i, lrt.last_run_time('foo').to_i
126
+ end
127
+ end
128
+ end
129
+
130
+ # Testing #connection, #connect, #connection_for, #reconnect, and #disconnect
131
+ # are testing Forwardable. Those tests are to be done in the individual
132
+ # connectors.
133
+ end
metadata ADDED
@@ -0,0 +1,283 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stockpile
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Austin Ziegler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rdoc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hoe-doofus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: hoe-gemspec2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hoe-git
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: hoe-travis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest-around
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-autotest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest-bisect
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.2'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.2'
139
+ - !ruby/object:Gem::Dependency
140
+ name: minitest-focus
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.1'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: minitest-moar
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: minitest-pretty_diff
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.1'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.1'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rake
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '10.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '10.0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: simplecov
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '0.7'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '0.7'
209
+ - !ruby/object:Gem::Dependency
210
+ name: hoe
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '3.13'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '3.13'
223
+ description: |-
224
+ Stockpile is a simple key-value store connection manager framework. Stockpile
225
+ itself does not implement a connection manager, but places expectations for
226
+ implemented connection managers. So far, only Redis has been implemented
227
+ (stockpile-redis).
228
+
229
+ Stockpile also provides an adapter so that its functionality can be accessed
230
+ from within a module.
231
+ email:
232
+ - halostatue@gmail.com
233
+ executables: []
234
+ extensions: []
235
+ extra_rdoc_files:
236
+ - Contributing.rdoc
237
+ - History.rdoc
238
+ - Licence.rdoc
239
+ - Manifest.txt
240
+ - README.rdoc
241
+ files:
242
+ - ".autotest"
243
+ - ".gemtest"
244
+ - ".minitest.rb"
245
+ - ".travis.yml"
246
+ - Contributing.rdoc
247
+ - Gemfile
248
+ - History.rdoc
249
+ - Licence.rdoc
250
+ - Manifest.txt
251
+ - README.rdoc
252
+ - Rakefile
253
+ - lib/stockpile.rb
254
+ - test/minitest_config.rb
255
+ - test/test_stockpile.rb
256
+ homepage: https://github.com/halostatue/stockpile/
257
+ licenses:
258
+ - MIT
259
+ metadata: {}
260
+ post_install_message:
261
+ rdoc_options:
262
+ - "--main"
263
+ - README.rdoc
264
+ require_paths:
265
+ - lib
266
+ required_ruby_version: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: 1.9.2
271
+ required_rubygems_version: !ruby/object:Gem::Requirement
272
+ requirements:
273
+ - - ">="
274
+ - !ruby/object:Gem::Version
275
+ version: '0'
276
+ requirements: []
277
+ rubyforge_project:
278
+ rubygems_version: 2.2.2
279
+ signing_key:
280
+ specification_version: 4
281
+ summary: Stockpile is a simple key-value store connection manager framework
282
+ test_files:
283
+ - test/test_stockpile.rb