stockpile 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []