stockpile 1.0 → 1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d12f955c01ed9d46dd13ec2fc357cbb086094146
4
- data.tar.gz: 5612ead9571a141b3b68695727655c1e03d680e0
3
+ metadata.gz: 9285056e00c41c3c09036585e9a8b2d5b517b27b
4
+ data.tar.gz: 84202f9f191b3ee26b30ed7deadef9203b6e5b8c
5
5
  SHA512:
6
- metadata.gz: a3734ac43b06cfe7fe5d3fdbdc60cbd9f7560dfdce10fdee63d1d8128291de176baa307b368ef39fc02fe9aa307fde5a552262f50455fb02ab4058ebd8c2a2a9
7
- data.tar.gz: 99989e77edc96cc5f193b1c327cf42c4c573b43250f236ffe018cbf4f23240cbc5c327065ae063eec4e99e9b090b8a3c188cea37c07dac8138a25f71a34aa221
6
+ metadata.gz: d0af27d554f66cd6e22006fdcfa75af45dd83305c5061a7fdf7b222e4836525124a9e6ec941dd9a8d95e743c576bb027f089d52be9b1532e78be2c1def10f47b
7
+ data.tar.gz: c60a70effa8e3e76b0b764807308db584543dfe61df6d519f4dd7344003db56298bd0c69b52c2ffbdb20aacedbe91ece8f5bf3d11a8980f2d26bc463d4bd01df
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  language: ruby
3
3
  rvm:
4
+ - 2.2.0
4
5
  - 2.1.0
5
6
  - 2.0.0
6
7
  - 1.9.3
@@ -29,6 +30,6 @@ after_script:
29
30
  notifications:
30
31
  email:
31
32
  recipients:
32
- - FIX@example.com
33
+ - halostatue@gmail.com
33
34
  on_success: change
34
35
  on_failure: always
data/History.rdoc CHANGED
@@ -1,3 +1,22 @@
1
+ === 1.1 / 2015-02-10
2
+
3
+ * 3 minor enhancements
4
+
5
+ * Created a base adapter, Stockpile::Base, that implements the core public
6
+ interface of a connection manager in a consistent way for argument parsing.
7
+
8
+ * Created a memory adapter as an example adapter. (This had previously been
9
+ a test adapter created in the test environment.)
10
+
11
+ * Documented changes to how clients can be specified for Stockpile.new,
12
+ Stockpile#connect, and Stockpile#connection_for.
13
+
14
+ * 1 bugfix
15
+
16
+ * Fix {issue #2}[https://github.com/halostatue/stockpile/issues/2], where
17
+ the use of the cache adapter causes the Stockpile cache manager to
18
+ initialize too early.
19
+
1
20
  === 1.0 / 2015-01-21
2
21
 
3
22
  * 1 major enhancement
data/Manifest.txt CHANGED
@@ -10,5 +10,8 @@ Manifest.txt
10
10
  README.rdoc
11
11
  Rakefile
12
12
  lib/stockpile.rb
13
+ lib/stockpile/base.rb
14
+ lib/stockpile/memory.rb
13
15
  test/minitest_config.rb
14
16
  test/test_stockpile.rb
17
+ test/test_stockpile_base.rb
data/README.rdoc CHANGED
@@ -14,6 +14,12 @@ implemented connection managers. So far, only Redis has been implemented
14
14
  Stockpile also provides an adapter so that its functionality can be accessed
15
15
  from within a module.
16
16
 
17
+ Release 1.1 fixes an issue with early initialization of an injected Stockpile
18
+ instance during adaptation
19
+ ({stockpile#2}[https://githbub.com/halostatue/stockpile/issues/2]). Several
20
+ small improvements to Stockpile.new, Stockpile#connect, and
21
+ Stockpile#connection_for have been documented.
22
+
17
23
  == Features
18
24
 
19
25
  * Stockpile manages key-value store connections. There are two variants:
@@ -35,7 +41,7 @@ your Gemfile.
35
41
 
36
42
  == Synopsis
37
43
 
38
- wide = Stockpile.new # A Stockpile to Redis.
44
+ wide = Stockpile.new # A Stockpile instance.
39
45
  wide.connection.set('hello', 'world') # => 'OK'
40
46
  wide.connection.get('hello') # => 'world'
41
47
 
@@ -51,6 +57,12 @@ your Gemfile.
51
57
  narrow.connection_for(:resque) != narrow.connection # => true
52
58
  narrow.connection_for(:resque).redis == narrow.connection # => true
53
59
 
60
+ # Standard namespace handling.
61
+ narrow.connection_for(:other, namespace: 'other') !=
62
+ narrow.connection # => true
63
+ narrow.connection_for(:other, namespace: 'other').redis !=
64
+ narrow.connection # => true
65
+
54
66
  # Show a Stockpile with no adapter capabilities, but name the method
55
67
  # stockpile, not cache. This will still usefully manage connections.
56
68
  module Cacher
@@ -132,10 +144,10 @@ be used in Rails. A link to it will be provided here when it is complete.
132
144
  == Install
133
145
 
134
146
  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.
147
+ key-value store specific connection manager. Instead, install a store-specific
148
+ gem which depends on Stockpile.
137
149
 
138
- gem 'stockpile-redis', '~> 1.0'
150
+ gem 'stockpile-redis', '~> 1.1'
139
151
 
140
152
  Or manually install:
141
153
 
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
+ require 'rake/clean'
5
6
 
6
7
  Hoe.plugin :doofus
7
8
  Hoe.plugin :gemspec2
@@ -46,6 +47,7 @@ namespace :test do
46
47
  ].join('; ')
47
48
  Rake::Task['test'].execute
48
49
  end
50
+ CLOBBER << 'coverage'
49
51
  end
50
52
 
51
53
  # vim: syntax=ruby
data/lib/stockpile.rb CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'forwardable'
4
4
 
5
- # Stockpile is a thin wrapper around connections to a fast key-value store used
6
- # for caching (currently only supporting Redis).
5
+ # Stockpile is a thin wrapper around connections to a fast key-value store
6
+ # used for caching (currently only supporting Redis).
7
7
  #
8
8
  # This provides a couple of layers of functionality:
9
9
  #
@@ -14,7 +14,7 @@ require 'forwardable'
14
14
  class Stockpile
15
15
  extend Forwardable
16
16
 
17
- VERSION = "1.0" # :nodoc:
17
+ VERSION = "1.1" # :nodoc:
18
18
 
19
19
  @default_manager = nil
20
20
 
@@ -79,20 +79,34 @@ class Stockpile
79
79
  end
80
80
 
81
81
  name = options.fetch(:method, :cache).to_sym
82
- msc = mod.singleton_class
83
-
82
+ mklass = mod.singleton_class
84
83
  default = options.fetch(:default_manager, nil)
85
84
 
86
- msc.send(:define_method, name) do |init_options = {}|
85
+ mklass.send(:define_method, name) do |init_options = {}|
87
86
  init_options = init_options.merge(default_manager: default)
88
87
  @__stockpile__ ||= ::Stockpile.new(init_options)
88
+ @__stockpile_triggers__ ||= []
89
+
90
+ triggers, @__stockpile_triggers__ = @__stockpile_triggers__, []
91
+ triggers.each(&:call)
92
+
93
+ @__stockpile__
89
94
  end
90
95
 
91
96
  if options.fetch(:adaptable, true)
92
97
  adapter = :"#{name}_adapter"
93
- msc.send(:define_method, adapter) do |m, k = nil|
98
+ mklass.send(:define_method, adapter) do |m, k = nil|
94
99
  o = self
95
- send(name).singleton_class.send(:include, m)
100
+
101
+ @__stockpile_triggers__ ||= []
102
+
103
+ trigger = -> { send(name).singleton_class.send(:include, m) }
104
+
105
+ if defined?(@__stockpile__) && @__stockpile__
106
+ trigger.call
107
+ else
108
+ @__stockpile_triggers__ << trigger
109
+ end
96
110
 
97
111
  if k
98
112
  mk = k.singleton_class
@@ -104,7 +118,7 @@ class Stockpile
104
118
  end
105
119
  end
106
120
 
107
- msc.send(:define_method, :"#{adapter}!") do |m|
121
+ mklass.send(:define_method, :"#{adapter}!") do |m|
108
122
  send(adapter, m, m)
109
123
  end
110
124
  end
@@ -131,37 +145,46 @@ class Stockpile
131
145
  # through any means.
132
146
  # +clients+:: Connections will be created for the provided list of clients.
133
147
  # These connections must be assigned to their appropriate clients
134
- # after initialization. This may also be called +client+.
148
+ # after initialization. This may also be called +client+. These
149
+ # values may be provided as names (e.g., +:cache+), or as hashes
150
+ # of client name to client options (e.g., <tt>{ cache: {
151
+ # namespace: 'x' } }</tt>). See Stockpile#connect for more
152
+ # details on this latter format.
135
153
  #
136
154
  # All other options will be passed to the connection provider.
137
155
  #
138
156
  # === Synopsis
139
157
  #
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|
158
+ # # Create and assign a connection to Redis.current, Resque, and Rollout.
159
+ # # Under a narrow connection management width, all three will be the
160
+ # # same client connection.
161
+ # options = {
162
+ # manager: Stockpile::Redis,
163
+ # clients: [ :redis, :resque ]
164
+ # }
165
+ # Stockpile.new(options) do |stockpile|
144
166
  # Redis.current = stockpile.connection_for(:redis)
145
167
  # Resque.redis = stockpile.connection_for(:resque)
146
168
  # # Clients will be created by name if necessary.
147
169
  # $rollout = Rollout.new(stockpile.connection_for(:rollout))
148
170
  # end
149
171
  def initialize(options = {})
150
- manager = options.fetch(:manager) {
151
- options[:default_manager] || self.class.default_manager
152
- }
172
+ options = options.dup
173
+ manager = options.delete(:manager)
174
+ default = options.delete(:default_manager) || self.class.default_manager
153
175
 
154
- unless manager
176
+ unless manager || default
155
177
  raise ArgumentError, "No connection manager provided or set as default."
156
178
  end
157
179
 
158
- clients = (Array(options[:clients]) + Array(options[:client])).flatten.uniq
180
+ manager ||= default
181
+
182
+ clients = [
183
+ Array(options.delete(:clients)), Array(options.delete(:client))
184
+ ].flatten.uniq
159
185
 
160
- options = { narrow: Stockpile.narrow? }.merge(options.reject { |k, _|
161
- k == :manager || k == :clients || k == :client
162
- })
186
+ @manager = manager.new({ narrow: Stockpile.narrow? }.merge(options))
163
187
 
164
- @manager = manager.new(options)
165
188
  connect(*clients)
166
189
  yield self if block_given?
167
190
  end
@@ -183,19 +206,57 @@ class Stockpile
183
206
  #
184
207
  # If the connection is using a narrow connection width, the same connection
185
208
  # will be shared.
209
+ #
210
+ # === Clients
211
+ #
212
+ # +clients+ may be provided in one of several ways:
213
+ #
214
+ # * A Hash object, mapping client names to client options.
215
+ #
216
+ # connect(redis: nil, rollout: { namespace: 'rollout' })
217
+ # # Transforms into:
218
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
219
+ #
220
+ # * An (implicit) array of client names, for connections with no options
221
+ # provided.
222
+ #
223
+ # connect(:redis, :resque, :rollout)
224
+ # # Transforms into:
225
+ # # connect(redis: {}, resque: {}, rollout: {})
226
+ #
227
+ # * An array of Hash objects, mapping client names to client options.
228
+ #
229
+ # connect({ redis: nil },
230
+ # { rollout: { namespace: 'rollout' } })
231
+ # # Transforms into:
232
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
233
+ #
234
+ # * A mix of client names and Hash objects:
235
+ #
236
+ # connect(:redis, { rollout: { namespace: 'rollout' } })
237
+ # # Transforms into:
238
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
239
+ #
240
+ # ==== Client Options
241
+ #
242
+ # Stockpile cache providers will handle the parsing of options to ensure that
243
+ # only suitable options are passed along (most providers will ignore any
244
+ # options that change the target system).
186
245
  def_delegator :@manager, :connect
187
246
 
188
247
  ##
189
248
  # :method: connection_for
190
249
  # :call-seq:
191
250
  # connection_for(client_name)
251
+ # connection_for(client_name, options)
192
252
  #
193
253
  # Returns a connection for a particular client. If the connection manager is
194
254
  # using a narrow connection width, this returns the same as #connection.
195
255
  #
196
256
  # The +client_name+ of +:all+ will always return +nil+.
197
257
  #
198
- # If the requested client does not yet exist, the connection will be created.
258
+ # If the requested client does not yet exist, the connection will be created
259
+ # with the provided options.
199
260
  def_delegator :@manager, :connection_for
200
261
 
201
262
  ##
@@ -0,0 +1,186 @@
1
+ # coding: utf-8
2
+
3
+ require 'stockpile'
4
+
5
+ class Stockpile
6
+ # The base connection manager, providing some common functionality for
7
+ # connection managers.
8
+ #
9
+ # It is not necessary to implement a connection manager from Stockpile::Base,
10
+ # but it is recommended because it provides all of the core functionality
11
+ # required of all connection managers, and requires only four integration
12
+ # points:
13
+ #
14
+ # * +initialize+: The constructor for the client connection manager. This
15
+ # should call +super+ at the beginning of its own implementation, and use
16
+ # <tt>@options</tt> if further parsing or manipulation of provided options
17
+ # is required.
18
+ # * +client_connect+: The actions necessary to connect clients.
19
+ # * +client_reconnect+: The actions necessary to reconnect clients.
20
+ # * +client_disconnect+: The actions necessary to disconnect clients.
21
+ class Base
22
+ # Create a new connection manager with the provided options.
23
+ #
24
+ # == Options
25
+ #
26
+ # +narrow+:: Use a narrow connection width if true; if not provided,
27
+ # uses the value of ::Stockpile.narrow? in this connection
28
+ # manager.
29
+ def initialize(options = {})
30
+ @options = options.dup
31
+ @narrow = !!@options.fetch(:narrow, ::Stockpile.narrow?)
32
+ @connection = nil
33
+ @clients = {}
34
+
35
+ @options.delete(:narrow)
36
+ end
37
+
38
+ # The primary connection.
39
+ attr_reader :connection
40
+
41
+ # Indicates if this connection manager is using a narrow connection width.
42
+ def narrow?
43
+ @narrow
44
+ end
45
+
46
+ # Connect unless already connected. Additional client connections can be
47
+ # specified in the parameters as a shorthand for calls to #connection_for.
48
+ #
49
+ # If #narrow? is true, the same connection will be used for all clients
50
+ # managed by this connection manager.
51
+ #
52
+ # manager.connect
53
+ # manager.connection_for(:bar)
54
+ #
55
+ # # This means the same as above.
56
+ # manager.connect(:bar)
57
+ def connect(*client_names)
58
+ @connection ||= client_connect
59
+
60
+ clients_from(*client_names).each { |client_name, options|
61
+ connection_for(client_name, options || {})
62
+ }
63
+
64
+ connection
65
+ end
66
+
67
+ # Perform a client connection for a specific +client_name+. A +client_name+
68
+ # of +:all+ will always return +nil+. If the requested client does not yet
69
+ # exist, the connection will be created.
70
+ def connection_for(client_name, options = {})
71
+ connect unless connection
72
+ return nil if client_name == :all
73
+
74
+ @clients[client_name] ||= client_connect(client_name, options)
75
+ end
76
+
77
+ # Reconnect some or all clients. The primary connection will always be
78
+ # reconnected; other clients will be reconnected based on the +clients+
79
+ # provided. Only clients actively managed by previous calls to #connect
80
+ # or #connection_for will be reconnected.
81
+ #
82
+ # If #reconnect is called with the value +:all+, all currently managed
83
+ # clients will be reconnected.
84
+ #
85
+ # If #narrow? is true, the primary connection will be reconnected, which
86
+ # reconnects all connections implicitly.
87
+ def reconnect(*client_names)
88
+ return unless connection
89
+
90
+ client_reconnect
91
+
92
+ unless narrow?
93
+ clients_from(*client_names).each { |client_name, _|
94
+ client_reconnect(@clients[client_name])
95
+ }
96
+ end
97
+
98
+ connection
99
+ end
100
+
101
+ # Disconnect for some or all clients. The primary connection will always
102
+ # be disconnected; other clients will be disconnected based on the
103
+ # +clients+ provided. Only clients actively managed by previous calls to
104
+ # #connect or #connection_for will be disconnected.
105
+ #
106
+ # If #disconnect is called with the value +:all+, all currently managed
107
+ # clients will be disconnected.
108
+ #
109
+ # If #narrow? is true, the primary connection will be disconnected,
110
+ # which disconnects all connections implicitly.
111
+ def disconnect(*client_names)
112
+ return unless connection
113
+
114
+ unless narrow?
115
+ clients_from(*client_names).each { |client_name, _|
116
+ client_disconnect(@clients[client_name])
117
+ }
118
+ end
119
+
120
+ client_disconnect
121
+ end
122
+
123
+ private
124
+ # Converts +client_names+ into a hash of client names to options.
125
+ #
126
+ # * A Hash object, mapping client names to client options.
127
+ #
128
+ # connect(redis: nil, rollout: { namespace: 'rollout' })
129
+ # # Transforms into:
130
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
131
+ #
132
+ # * An (implicit) array of client names, for connections with no options
133
+ # provided.
134
+ #
135
+ # connect(:redis, :resque, :rollout)
136
+ # # Transforms into:
137
+ # # connect(redis: {}, resque: {}, rollout: {})
138
+ #
139
+ # * An array of Hash objects, mapping client names to client options.
140
+ #
141
+ # connect({ redis: nil },
142
+ # { rollout: { namespace: 'rollout' } })
143
+ # # Transforms into:
144
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
145
+ #
146
+ # * A mix of client names and Hash objects:
147
+ #
148
+ # connect(:redis, { rollout: { namespace: 'rollout' } })
149
+ # # Transforms into:
150
+ # # connect(redis: {}, rollout: { namespace: 'rollout' })
151
+ def clients_from(*client_names)
152
+ clients = if client_names.size == 1
153
+ if client_names.first == :all
154
+ @clients.keys
155
+ else
156
+ client_names
157
+ end
158
+ else
159
+ client_names
160
+ end
161
+ clients.map { |v|
162
+ case v
163
+ when Hash
164
+ v
165
+ else
166
+ { v => {} }
167
+ end
168
+ }.inject({}, :merge)
169
+ end
170
+
171
+ # Performs a client connect action. Must be implemented by a client.
172
+ def client_connect(name = nil, options = {})
173
+ raise NotImplementedError
174
+ end
175
+
176
+ # Performs a client reconnect action. Must be implemented by a client.
177
+ def client_reconnect(client = connect())
178
+ raise NotImplementedError
179
+ end
180
+
181
+ # Performs a client disconnect action. Must be implemented by a client.
182
+ def client_disconnect(client = connect())
183
+ raise NotImplementedError
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,170 @@
1
+ # -*- ruby encoding: utf-8 -*-
2
+
3
+ require 'stockpile/base'
4
+
5
+ class Stockpile
6
+ # An in-memory connection manager, providing a complete example of how a
7
+ # Stockpile connection manager could be made.
8
+ class Memory < Stockpile::Base
9
+ class Data # :nodoc:
10
+ class << self
11
+ attr_reader :data
12
+
13
+ def reset
14
+ @data = {}
15
+ end
16
+ end
17
+
18
+ reset
19
+
20
+ attr_reader :options
21
+
22
+ def initialize(options = {})
23
+ @connected = false
24
+ @options = options
25
+ connect
26
+ end
27
+
28
+ def connect
29
+ @connected = true
30
+ end
31
+ alias_method :reconnect, :connect
32
+
33
+ def disconnect
34
+ @connected = false
35
+ end
36
+
37
+ def connected?
38
+ !!@connected
39
+ end
40
+
41
+ def get(key)
42
+ check_valid!
43
+ self.class.data[key]
44
+ end
45
+
46
+ def set(key, value)
47
+ check_valid!
48
+ self.class.data[key] = value
49
+ end
50
+
51
+ def hget(key, field)
52
+ raise unless connected?
53
+ valid_hkey!(key)[field]
54
+ end
55
+
56
+ def hset(key, field, value)
57
+ raise unless connected?
58
+ valid_hkey!(key)[field] = value
59
+ end
60
+
61
+ private
62
+ def check_valid!
63
+ raise unless connected?
64
+ end
65
+
66
+ def valid_hkey!(key)
67
+ check_valid!
68
+ case h = self.class.data[key]
69
+ when nil
70
+ h = (self.class.data[key] = {})
71
+ when Hash
72
+ nil
73
+ else
74
+ raise
75
+ end
76
+ h
77
+ end
78
+ end
79
+
80
+ ##
81
+ # :singleton-method: new
82
+ # :call-seq:
83
+ # new(options = {})
84
+ #
85
+ # Create a new Memory connection manager.
86
+
87
+ ##
88
+ # :attr_reader: connection
89
+ #
90
+ # The primary connection.
91
+
92
+ ##
93
+ # :method: narrow?
94
+ #
95
+ # Indicates if this connection manager is using a narrow connection width.
96
+
97
+ ##
98
+ # :method: connect
99
+ # :call-seq:
100
+ # connect(*client_names)
101
+ #
102
+ # Connect unless already connected. Additional client connections can be
103
+ # specified in the parameters as a shorthand for calls to #connection_for.
104
+ #
105
+ # If #narrow? is true, the same connection will be used for all clients
106
+ # managed by this connection manager.
107
+ #
108
+ # manager.connect
109
+ # manager.connection_for(:bar)
110
+ #
111
+ # # This means the same as above.
112
+ # manager.connect(:bar)
113
+
114
+ ##
115
+ # :method: connection_for
116
+ # :call-seq:
117
+ # connection_for(client_name, options = {})
118
+ #
119
+ # Perform a client connection for a specific +client_name+. A +client_name+
120
+ # of +:all+ will always return +nil+. If the requested client does not yet
121
+ # exist, the connection will be created.
122
+
123
+ ##
124
+ # :method: reconnect
125
+ # :call-seq:
126
+ # reconnect(*client_names)
127
+ #
128
+ # Reconnect some or all clients. The primary connection will always be
129
+ # reconnected; other clients will be reconnected based on the +clients+
130
+ # provided. Only clients actively managed by previous calls to #connect or
131
+ # #connection_for will be reconnected.
132
+ #
133
+ # If #reconnect is called with the value +:all+, all currently managed
134
+ # clients will be reconnected.
135
+ #
136
+ # If #narrow? is true, the primary connection will be reconnected, which
137
+ # reconnects all connections implicitly.
138
+
139
+ ##
140
+ # :method: disconnect
141
+ # :call-seq:
142
+ # disconnect(*client_names)
143
+ #
144
+ # Disconnect for some or all clients. The primary connection will always
145
+ # be disconnected; other clients will be disconnected based on the
146
+ # +clients+ provided. Only clients actively managed by previous calls to
147
+ # #connect or #connection_for will be disconnected.
148
+ #
149
+ # If #disconnect is called with the value +:all+, all currently managed
150
+ # clients will be disconnected.
151
+ #
152
+ # If #narrow? is true, the primary connection will be disconnected,
153
+ # which disconnects all connections implicitly.
154
+
155
+ ##
156
+ private
157
+ def client_connect(name = nil, options = {})
158
+ return connection if connection && narrow?
159
+ Data.new(@options.merge(options))
160
+ end
161
+
162
+ def client_reconnect(client = connection())
163
+ client.reconnect if client
164
+ end
165
+
166
+ def client_disconnect(client = connection())
167
+ client.disconnect if client
168
+ end
169
+ end
170
+ end
@@ -7,138 +7,12 @@ require 'minitest/focus'
7
7
  require 'minitest/moar'
8
8
  require 'minitest/bisect'
9
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
10
+ require 'stockpile/memory'
137
11
 
138
12
  module Minitest::ENVStub
139
13
  def setup
140
14
  super
141
- StockpileTestManager::Connection.reset_data
15
+ Stockpile::Memory::Data.reset
142
16
  end
143
17
 
144
18
  def stub_env env, options = {}, *block_args, &block
@@ -1,5 +1,4 @@
1
1
  require 'minitest_config'
2
- require 'stockpile'
3
2
  require 'time'
4
3
 
5
4
  describe Stockpile do
@@ -18,6 +17,7 @@ describe Stockpile do
18
17
  end
19
18
 
20
19
  describe ".inject!" do
20
+ let(:cls) { Class.new }
21
21
  let(:mod) { Module.new }
22
22
  let(:lrt) {
23
23
  Module.new do
@@ -29,10 +29,24 @@ describe Stockpile do
29
29
  Time.parse(value) if value
30
30
  end
31
31
  end
32
+
33
+ def job_ran(job, value = nil)
34
+ if value
35
+ connection.set(job, !!value)
36
+ else
37
+ !!connection.get(job)
38
+ end
39
+ end
32
40
  end
33
41
  }
34
42
 
35
- describe "Stockpile.inject!(Mod):" do
43
+ it "throws an ArgumentError unless it's a class or module" do
44
+ assert_raises ArgumentError do
45
+ ::Stockpile.inject!(Object.new)
46
+ end
47
+ end
48
+
49
+ describe "Stockpile.inject!(Module):" do
36
50
  before { ::Stockpile.inject!(mod) }
37
51
 
38
52
  it "defines Mod.cache" do
@@ -84,7 +98,9 @@ describe Stockpile do
84
98
  end
85
99
 
86
100
  describe "Stockpile.inject!(Mod, method: :stockpile, adaptable: false)" do
87
- before { ::Stockpile.inject!(mod, method: :stockpile, adaptable: false) }
101
+ before do
102
+ ::Stockpile.inject!(mod, method: :stockpile, adaptable: false)
103
+ end
88
104
 
89
105
  it "defines Mod.stockpile" do
90
106
  assert_respond_to mod, :stockpile
@@ -100,8 +116,29 @@ describe Stockpile do
100
116
  let(:now) { Time.now }
101
117
  let(:iso) { now.utc.iso8601 }
102
118
  before do
103
- ::Stockpile.inject!(mod, adaptable: true,
104
- default_manager: StockpileTestManager)
119
+ ::Stockpile.inject!(mod, default_manager: Stockpile::Memory)
120
+ end
121
+
122
+ it "queues adaptation until Stockpile has been initialized" do
123
+ stub Stockpile, :new do
124
+ mod.cache_adapter(lrt)
125
+ refute_called Stockpile, :new
126
+ refute_nil mod.instance_variable_get(:@__stockpile_triggers__)
127
+ refute_empty mod.instance_variable_get(:@__stockpile_triggers__)
128
+ end
129
+
130
+ assert_equal({ namespace: 'n' },
131
+ mod.cache(namespace: 'n').connection.options)
132
+ assert_respond_to mod.cache, :last_run_time
133
+ assert_respond_to mod.cache, :job_ran
134
+ end
135
+
136
+ it "adapts an initialized Stockpile immediately" do
137
+ mod.cache
138
+ mod.cache_adapter(lrt)
139
+ assert_respond_to mod.cache, :last_run_time
140
+ assert_respond_to mod.cache, :job_ran
141
+ assert_empty mod.instance_variable_get(:@__stockpile_triggers__)
105
142
  end
106
143
 
107
144
  it "adapts the cache with last_run_time" do
@@ -124,10 +161,49 @@ describe Stockpile do
124
161
  assert_equal iso, lrt.last_run_time('foo', now)
125
162
  assert_equal now.to_i, lrt.last_run_time('foo').to_i
126
163
  end
164
+
165
+ it "adapts the cache with job_ran" do
166
+ mod.cache_adapter(lrt)
167
+ refute mod.cache.job_ran('foo')
168
+ assert mod.cache.job_ran('foo', true)
169
+ assert mod.cache.job_ran('foo')
170
+ end
171
+
172
+ it "adapts the module with last_run_time" do
173
+ mod.cache_adapter(lrt, mod)
174
+ refute mod.job_ran('foo')
175
+ assert mod.job_ran('foo', true)
176
+ assert mod.job_ran('foo')
177
+ end
178
+
179
+ it "adapts the lrt module with last_run_time" do
180
+ mod.cache_adapter!(lrt)
181
+ refute lrt.job_ran('foo')
182
+ assert lrt.job_ran('foo', true)
183
+ assert lrt.job_ran('foo')
184
+ end
127
185
  end
128
- end
129
186
 
130
- # Testing #connection, #connect, #connection_for, #reconnect, and #disconnect
131
- # are testing Forwardable. Those tests are to be done in the individual
132
- # connectors.
187
+ describe "Stockpile.inject!(Class):" do
188
+ before { ::Stockpile.inject!(cls) }
189
+
190
+ it "defines cls.cache" do
191
+ assert_respond_to cls, :cache
192
+ end
193
+
194
+ it "defines cls.cache_adapter" do
195
+ assert_respond_to cls, :cache_adapter
196
+ end
197
+
198
+ it "defines cls.cache_adapter!" do
199
+ assert_respond_to cls, :cache_adapter!
200
+ end
201
+
202
+ it "Fails cache initialization" do
203
+ assert_raises ArgumentError do
204
+ cls.cache
205
+ end
206
+ end
207
+ end
208
+ end
133
209
  end
@@ -0,0 +1,245 @@
1
+ require 'minitest_config'
2
+
3
+ describe Stockpile::Base do
4
+ def assert_clients expected_clients, connector
5
+ actual_clients = connector.instance_variable_get(:@clients).keys
6
+ assert_equal actual_clients.sort, expected_clients.sort
7
+ end
8
+
9
+ let(:mem) {
10
+ stub_env({}) { Stockpile::Memory.new }
11
+ }
12
+ let(:mem_namespace) {
13
+ stub_env({}) { Stockpile::Memory.new(namespace: 'Z') }
14
+ }
15
+ let(:mem_wide) {
16
+ stub_env({}) { Stockpile::Memory.new(narrow: false) }
17
+ }
18
+ let(:mem_narrow) {
19
+ stub_env({}) { Stockpile::Memory.new(narrow: true) }
20
+ }
21
+
22
+ describe 'constructor' do
23
+ it "uses the default connection width by default" do
24
+ stub ::Stockpile, :narrow?, lambda { false } do
25
+ refute Stockpile::Base.new.narrow?,
26
+ "should be narrow, but is not"
27
+ end
28
+
29
+ stub ::Stockpile, :narrow?, lambda { true } do
30
+ assert Stockpile::Base.new.narrow?,
31
+ "is not narrow, but should be"
32
+ end
33
+ end
34
+
35
+ it "can be told which connection width to use explicitly" do
36
+ stub ::Stockpile, :narrow?, lambda { false } do
37
+ assert mem_narrow.narrow?
38
+ end
39
+
40
+ stub ::Stockpile, :narrow?, lambda { true } do
41
+ refute mem_wide.narrow?
42
+ end
43
+ end
44
+
45
+ it "passes settings through to the client" do
46
+ options = {
47
+ url: 'test://xyz/'
48
+ }
49
+ mem = ::Stockpile::Memory.new(options)
50
+ assert_equal 'test://xyz/', mem.connect.options[:url]
51
+ end
52
+
53
+ it "has no clients by default" do
54
+ assert_clients [], ::Stockpile::Memory.new
55
+ end
56
+ end
57
+
58
+ describe "#connect" do
59
+ it "raises NotImplementedError unless #client_connect is implemented" do
60
+ assert_raises NotImplementedError do
61
+ ::Stockpile::Base.new.connect
62
+ end
63
+ end
64
+
65
+ it "creates a connection to the client" do
66
+ assert_nil mem.connection
67
+ refute_nil mem.connect
68
+ end
69
+
70
+ it "creates a namespaced connection to the client" do
71
+ assert_nil mem_namespace.connection
72
+ refute_nil mem_namespace.connect
73
+ end
74
+
75
+ describe "with a wide connection width" do
76
+ before do
77
+ mem_wide.connect(:hoge, { quux: {} })
78
+ end
79
+
80
+ it "connects multiple clients" do
81
+ assert_clients [ :hoge, :quux ], mem_wide
82
+ end
83
+
84
+ it "connects *different* clients" do
85
+ refute_same mem_wide.connection, mem_wide.connection_for(:hoge)
86
+ refute_same mem_wide.connection, mem_wide.connection_for(:quux)
87
+ refute_same mem_wide.connection_for(:hoge), mem_wide.connection_for(:quux)
88
+ end
89
+ end
90
+
91
+ describe "with a narrow connection width" do
92
+ before do
93
+ mem_narrow.connect(:hoge, :quux)
94
+ end
95
+
96
+ it "appears to connect multiple clients" do
97
+ assert_clients [ :hoge, :quux ], mem_narrow
98
+ end
99
+
100
+ it "returns identical clients" do
101
+ assert_same mem_narrow.connection, mem_narrow.connection_for(:hoge)
102
+ assert_same mem_narrow.connection, mem_narrow.connection_for(:quux)
103
+ assert_same mem_narrow.connection_for(:hoge), mem_narrow.connection_for(:quux)
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "#connection_for" do
109
+ it "raises NotImplementedError unless #client_connect is implemented" do
110
+ assert_raises NotImplementedError do
111
+ instance_stub ::Stockpile::Base, :connection, -> { true } do
112
+ ::Stockpile::Base.new.connection_for(:foo)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "with a wide connection width" do
118
+ it "connects the main client" do
119
+ mem_wide.connection_for(:global)
120
+ assert mem_wide.connection
121
+ refute_same mem_wide.connection, mem_wide.connection_for(:global)
122
+ end
123
+ end
124
+
125
+ describe "with a narrow connection width" do
126
+ it "connects the main client" do
127
+ mem_narrow.connection_for(:global)
128
+ assert mem_narrow.connection
129
+ assert_same mem_narrow.connection, mem_narrow.connection_for(:global)
130
+ end
131
+ end
132
+ end
133
+
134
+ let(:connection) { Stockpile::Memory::Data }
135
+
136
+ describe "#disconnect" do
137
+ it "raises NotImplementedError unless #client_disconnect is implemented" do
138
+ base = ::Stockpile::Base.new
139
+ assert_raises NotImplementedError do
140
+ instance_stub ::Stockpile::Base, :connect do
141
+ instance_stub ::Stockpile::Base, :connection, -> { true } do
142
+ base.disconnect
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ describe "with a wide connection width" do
149
+ let(:global) { mem_wide.connection }
150
+ let(:hoge) { mem_wide.connection_for(:hoge) }
151
+
152
+ before do
153
+ mem_wide.connect(:hoge)
154
+ assert hoge.connected? && global.connected?
155
+ end
156
+
157
+ it "disconnects the global client" do
158
+ mem_wide.disconnect
159
+ assert hoge.connected? && !global.connected?
160
+ end
161
+
162
+ it "disconnects the redis and global clients" do
163
+ mem_wide.disconnect(:hoge)
164
+ refute hoge.connected? || global.connected?
165
+ end
166
+ end
167
+
168
+ describe "with a narrow connection width" do
169
+ let(:global) { mem_narrow.connection }
170
+ let(:hoge) { mem_narrow.connection_for(:hoge) }
171
+
172
+ before do
173
+ mem_narrow.connect(:hoge)
174
+ assert hoge.connected? && global.connected?
175
+ end
176
+
177
+ it "#disconnect disconnects all clients" do
178
+ mem_narrow.disconnect
179
+ refute hoge.connected? || global.connected?
180
+ end
181
+
182
+ it "#disconnect(:hoge) disconnects all clients" do
183
+ mem_narrow.disconnect(:hoge)
184
+ refute hoge.connected? || global.connected?
185
+ end
186
+ end
187
+ end
188
+
189
+ describe "#reconnect" do
190
+ it "raises NotImplementedError unless #client_reconnect is implemented" do
191
+ base = ::Stockpile::Base.new
192
+ assert_raises NotImplementedError do
193
+ instance_stub ::Stockpile::Base, :connect do
194
+ instance_stub ::Stockpile::Base, :connection, -> { true } do
195
+ base.reconnect
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ describe "with a wide connection width" do
202
+ let(:global) { mem_wide.connection }
203
+ let(:hoge) { mem_wide.connection_for(:hoge) }
204
+
205
+ before do
206
+ mem_wide.connect(:hoge)
207
+ assert hoge.connected? && global.connected?
208
+ mem_wide.disconnect(:all)
209
+ refute hoge.connected? || global.connected?
210
+ end
211
+
212
+ it "reconnects the global client" do
213
+ mem_wide.reconnect
214
+ assert !hoge.connected? && global.connected?
215
+ end
216
+
217
+ it "reconnects the redis and global clients" do
218
+ mem_wide.reconnect(:hoge)
219
+ assert hoge.connected? && global.connected?
220
+ end
221
+ end
222
+
223
+ describe "with a narrow connection width" do
224
+ let(:global) { mem_narrow.connection }
225
+ let(:hoge) { mem_narrow.connection_for(:hoge) }
226
+
227
+ def force_connection
228
+ mem_narrow.connect(:hoge)
229
+ assert hoge.connected? && global.connected?
230
+ mem_wide.disconnect(:all)
231
+ refute hoge.connected? || global.connected?
232
+ end
233
+
234
+ it "#reconnect reconnects the all clients" do
235
+ mem_narrow.reconnect
236
+ assert hoge.connected? && global.connected?
237
+ end
238
+
239
+ it "#reconnect(:hoge:) reconnects all clients" do
240
+ mem_narrow.reconnect(:hoge)
241
+ assert hoge.connected? && global.connected?
242
+ end
243
+ end
244
+ end
245
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stockpile
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: '1.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Ziegler
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-21 00:00:00.000000000 Z
11
+ date: 2015-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -228,6 +228,12 @@ description: |-
228
228
 
229
229
  Stockpile also provides an adapter so that its functionality can be accessed
230
230
  from within a module.
231
+
232
+ Release 1.1 fixes an issue with early initialization of an injected Stockpile
233
+ instance during adaptation
234
+ ({stockpile#2}[https://githbub.com/halostatue/stockpile/issues/2]). Several
235
+ small improvements to Stockpile.new, Stockpile#connect, and
236
+ Stockpile#connection_for have been documented.
231
237
  email:
232
238
  - halostatue@gmail.com
233
239
  executables: []
@@ -251,8 +257,11 @@ files:
251
257
  - README.rdoc
252
258
  - Rakefile
253
259
  - lib/stockpile.rb
260
+ - lib/stockpile/base.rb
261
+ - lib/stockpile/memory.rb
254
262
  - test/minitest_config.rb
255
263
  - test/test_stockpile.rb
264
+ - test/test_stockpile_base.rb
256
265
  homepage: https://github.com/halostatue/stockpile/
257
266
  licenses:
258
267
  - MIT
@@ -279,5 +288,4 @@ rubygems_version: 2.2.2
279
288
  signing_key:
280
289
  specification_version: 4
281
290
  summary: Stockpile is a simple key-value store connection manager framework
282
- test_files:
283
- - test/test_stockpile.rb
291
+ test_files: []