remix-stash 0.9.0 → 0.9.6
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.
- data/.gitignore +1 -0
- data/LICENSE +21 -0
- data/README.markdown +32 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/benchmarks/get_set.rb +35 -0
- data/benchmarks/payload.rb +125 -0
- data/examples/eval.rb +8 -0
- data/examples/gate.rb +6 -0
- data/examples/getset.rb +13 -0
- data/examples/scope.rb +17 -0
- data/examples/stash.rb +16 -0
- data/harness.rb +19 -0
- data/lib/remix/stash/cluster.rb +79 -0
- data/lib/remix/stash/extension.rb +7 -0
- data/lib/remix/stash/protocol.rb +139 -0
- data/lib/remix/stash.rb +256 -0
- data/remix-stash.gemspec +67 -0
- data/spec/extension_spec.rb +37 -0
- data/spec/spec.rb +10 -0
- data/spec/stash_spec.rb +411 -0
- metadata +36 -8
data/lib/remix/stash.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
module Remix; end
|
2
|
+
|
3
|
+
class Remix::Stash
|
4
|
+
require 'remix/stash/extension'
|
5
|
+
require 'remix/stash/cluster'
|
6
|
+
require 'remix/stash/protocol'
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
@@instances = {}
|
11
|
+
@@clusters = {:default => Cluster.new(%w[localhost:11211])}
|
12
|
+
|
13
|
+
def self.cluster(name)
|
14
|
+
@@clusters[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.cycle_action
|
18
|
+
@@instances.each {|name, stash|
|
19
|
+
stash.cycle if stash.default[:coherency] == :action}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define_cluster(clusters)
|
23
|
+
clusters.each do |k,v|
|
24
|
+
@@clusters[k] = Cluster === v ? v : Cluster.new(v)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.new(name)
|
29
|
+
@@instances[name] ||= super
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(name)
|
33
|
+
@name = name
|
34
|
+
@scope = nil
|
35
|
+
if name == :root
|
36
|
+
@local = @opts = {:coherency => :action, :ttl => 0, :cluster => :default}
|
37
|
+
else
|
38
|
+
@local = {}
|
39
|
+
@opts = stash.default.dup
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add(*keys)
|
44
|
+
opts = default_opts(keys)
|
45
|
+
value = keys.pop
|
46
|
+
key = canonical_key(keys, opts)
|
47
|
+
cluster(opts).select(key) {|io| Protocol.add(io, key, value, opts[:ttl])}
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear(*keys)
|
51
|
+
opts = default_opts(keys)
|
52
|
+
if keys.empty?
|
53
|
+
if @name == :root
|
54
|
+
cluster(opts).each {|io| Protocol.flush(io)}
|
55
|
+
else
|
56
|
+
vk = vector_key
|
57
|
+
cluster(opts).select(vk) {|io|
|
58
|
+
unless Protocol.incr(io, vk, 1)
|
59
|
+
Protocol.add(io, vk, '0')
|
60
|
+
Protocol.incr(io, vk, 1)
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
cycle
|
65
|
+
else
|
66
|
+
# remove a specific key
|
67
|
+
key = canonical_key(keys, opts)
|
68
|
+
cluster(opts).select(key) {|io| Protocol.delete(io, key)}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_scope
|
73
|
+
@scope = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def cycle
|
77
|
+
@vector = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def decr(*keys)
|
81
|
+
opts = default_opts(keys)
|
82
|
+
step = keys.pop
|
83
|
+
key = canonical_key(keys, opts)
|
84
|
+
cluster(opts).select(key) {|io| Protocol.decr(io, key, step)}
|
85
|
+
end
|
86
|
+
|
87
|
+
def default(opts = nil)
|
88
|
+
if opts
|
89
|
+
if opts.has_key? :coherency
|
90
|
+
[:dynamic, :action, :transaction].include?(opts[:coherency]) or raise ArgumentError,
|
91
|
+
"Invalid coherency setting used (#{opts[:coherency].inspect})"
|
92
|
+
end
|
93
|
+
@local.merge!(opts)
|
94
|
+
@opts.merge!(opts)
|
95
|
+
if @name == :root
|
96
|
+
@@instances.each do |name, stash|
|
97
|
+
stash.update_options unless name == :root
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@opts
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete(*keys)
|
105
|
+
opts = default_opts(keys)
|
106
|
+
key = canonical_key(keys, opts)
|
107
|
+
cluster(opts).select(key) {|io| Protocol.delete(io, key)}
|
108
|
+
end
|
109
|
+
|
110
|
+
def eval(*keys)
|
111
|
+
opts = default_opts(keys)
|
112
|
+
key = canonical_key(keys, opts)
|
113
|
+
cluster(opts).select(key) {|io|
|
114
|
+
value = Protocol.get(io, key)
|
115
|
+
if value
|
116
|
+
Marshal.load(value)
|
117
|
+
else
|
118
|
+
value = yield(*keys)
|
119
|
+
Protocol.set(io, key, dump_value(value), opts[:ttl])
|
120
|
+
value
|
121
|
+
end
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def gate(*keys)
|
126
|
+
opts = default_opts(keys)
|
127
|
+
key = canonical_key(keys, opts)
|
128
|
+
cluster(opts).select(key) {|io|
|
129
|
+
if Protocol.get(io, key)
|
130
|
+
yield(*keys)
|
131
|
+
true
|
132
|
+
else
|
133
|
+
false
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def get(*keys)
|
139
|
+
opts = default_opts(keys)
|
140
|
+
key = canonical_key(keys, opts)
|
141
|
+
cluster(opts).select(key) {|io| load_value(Protocol.get(io, key))}
|
142
|
+
end
|
143
|
+
alias [] get
|
144
|
+
|
145
|
+
def incr(*keys)
|
146
|
+
opts = default_opts(keys)
|
147
|
+
step = keys.pop
|
148
|
+
key = canonical_key(keys, opts)
|
149
|
+
cluster(opts).select(key) {|io| Protocol.incr(io, key, step)}
|
150
|
+
end
|
151
|
+
|
152
|
+
def read(*keys)
|
153
|
+
opts = default_opts(keys)
|
154
|
+
key = canonical_key(keys, opts)
|
155
|
+
cluster(opts).select(key) {|io| Protocol.get(io, key)}
|
156
|
+
end
|
157
|
+
|
158
|
+
def release
|
159
|
+
@@instances.delete(@name)
|
160
|
+
end
|
161
|
+
|
162
|
+
def scope(&b)
|
163
|
+
@scope = b
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def set(*keys)
|
168
|
+
opts = default_opts(keys)
|
169
|
+
value = keys.pop
|
170
|
+
key = canonical_key(keys, opts)
|
171
|
+
cluster(opts).select(key) {|io| Protocol.set(io, key, dump_value(value), opts[:ttl])}
|
172
|
+
end
|
173
|
+
alias []= set
|
174
|
+
|
175
|
+
def transaction
|
176
|
+
yield self
|
177
|
+
ensure
|
178
|
+
cycle
|
179
|
+
end
|
180
|
+
|
181
|
+
def write(*keys)
|
182
|
+
opts = default_opts(keys)
|
183
|
+
value = keys.pop
|
184
|
+
key = canonical_key(keys, opts)
|
185
|
+
cluster(opts).select(key) {|io| Protocol.set(io, key, value, opts[:ttl])}
|
186
|
+
end
|
187
|
+
|
188
|
+
protected
|
189
|
+
|
190
|
+
def update_options
|
191
|
+
@opts = stash.default.merge(@local)
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
KEY_SEPARATOR = '/'
|
197
|
+
def canonical_key(keys, opts)
|
198
|
+
v = vector(opts)
|
199
|
+
if @scope
|
200
|
+
"#{implicit_scope}#{keys.join(KEY_SEPARATOR)}#{vector(opts)}"
|
201
|
+
elsif v
|
202
|
+
keys.join(KEY_SEPARATOR) << v
|
203
|
+
else
|
204
|
+
keys.join(KEY_SEPARATOR)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def cluster(opts = {})
|
209
|
+
@@clusters[opts[:cluster]]
|
210
|
+
end
|
211
|
+
|
212
|
+
def default_opts(params)
|
213
|
+
params.last.is_a?(Hash) ? default.merge(params.pop) : default
|
214
|
+
end
|
215
|
+
|
216
|
+
def dump_value(value)
|
217
|
+
Marshal.dump(value)
|
218
|
+
end
|
219
|
+
|
220
|
+
def implicit_scope
|
221
|
+
@scope.call(self) if @scope
|
222
|
+
end
|
223
|
+
|
224
|
+
def load_value(data)
|
225
|
+
Marshal.load(data) if data
|
226
|
+
rescue TypeError, ArgumentError
|
227
|
+
logger = default_opts[:logger]
|
228
|
+
logger && logger.error("[stash] Unable to load marshal stream: #{data.inspect}")
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
|
232
|
+
def vector(opts)
|
233
|
+
return if @name == :root
|
234
|
+
return @vector if @vector && opts[:coherency] != :dynamic
|
235
|
+
vk = vector_key
|
236
|
+
cluster(opts).select(vk) do |io|
|
237
|
+
@vector = Protocol.get(io, vk)
|
238
|
+
unless @vector
|
239
|
+
Protocol.add(io, vk, '0')
|
240
|
+
@vector = Protocol.get(io, vk)
|
241
|
+
end
|
242
|
+
@vector = "@#@name:#@vector"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def vector_key
|
247
|
+
"#@name#{implicit_scope}_vector"
|
248
|
+
end
|
249
|
+
|
250
|
+
class ProtocolError < RuntimeError; end
|
251
|
+
class ClusterError < RuntimeError; end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
class Object; include Remix::Stash::Extension end
|
256
|
+
module Remix; extend Remix::Stash::Extension end
|
data/remix-stash.gemspec
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{remix-stash}
|
8
|
+
s.version = "0.9.6"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Brian Mitchell"]
|
12
|
+
s.date = %q{2009-09-22}
|
13
|
+
s.email = %q{binary42@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"LICENSE",
|
21
|
+
"README.markdown",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"benchmarks/get_set.rb",
|
25
|
+
"benchmarks/payload.rb",
|
26
|
+
"examples/eval.rb",
|
27
|
+
"examples/gate.rb",
|
28
|
+
"examples/getset.rb",
|
29
|
+
"examples/scope.rb",
|
30
|
+
"examples/stash.rb",
|
31
|
+
"harness.rb",
|
32
|
+
"lib/remix/stash.rb",
|
33
|
+
"lib/remix/stash/cluster.rb",
|
34
|
+
"lib/remix/stash/extension.rb",
|
35
|
+
"lib/remix/stash/protocol.rb",
|
36
|
+
"remix-stash.gemspec",
|
37
|
+
"spec/extension_spec.rb",
|
38
|
+
"spec/spec.rb",
|
39
|
+
"spec/stash_spec.rb"
|
40
|
+
]
|
41
|
+
s.has_rdoc = true
|
42
|
+
s.homepage = %q{http://github.com/binary42/remix-stash}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.1}
|
46
|
+
s.summary = %q{Remix your memcache}
|
47
|
+
s.test_files = [
|
48
|
+
"spec/extension_spec.rb",
|
49
|
+
"spec/spec.rb",
|
50
|
+
"spec/stash_spec.rb",
|
51
|
+
"examples/eval.rb",
|
52
|
+
"examples/gate.rb",
|
53
|
+
"examples/getset.rb",
|
54
|
+
"examples/scope.rb",
|
55
|
+
"examples/stash.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
60
|
+
s.specification_version = 2
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
63
|
+
else
|
64
|
+
end
|
65
|
+
else
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec'
|
2
|
+
|
3
|
+
class ExtensionSpec < Spec
|
4
|
+
|
5
|
+
context '#stash' do
|
6
|
+
|
7
|
+
should 'return a stash object with the correct name' do
|
8
|
+
s = stash(:a)
|
9
|
+
assert_instance_of Stash, s
|
10
|
+
assert_equal :a, s.name
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'return the same object when given the same name' do
|
14
|
+
assert_equal stash(:b), stash(:b)
|
15
|
+
assert_not_equal stash(:a), stash(:b)
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'allow access to a default root stash' do
|
19
|
+
assert_equal stash, stash(:root)
|
20
|
+
assert_equal :root, stash.name
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'modules' do
|
26
|
+
|
27
|
+
should 'be mixed into Object' do
|
28
|
+
assert Object.respond_to?(:stash)
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'be mixed into Remix' do
|
32
|
+
assert Remix.respond_to?(:stash)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|