stockpile 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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