redis_object 0.5.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.
- data/.coveralls.yml +1 -0
- data/.gitignore +6 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/README.markdown +179 -0
- data/Rakefile +10 -0
- data/lib/redis_object.rb +47 -0
- data/lib/redis_object/base.rb +408 -0
- data/lib/redis_object/collection.rb +388 -0
- data/lib/redis_object/defaults.rb +42 -0
- data/lib/redis_object/experimental/history.rb +49 -0
- data/lib/redis_object/ext/benchmark.rb +34 -0
- data/lib/redis_object/ext/cleaner.rb +14 -0
- data/lib/redis_object/ext/filters.rb +68 -0
- data/lib/redis_object/ext/script_cache.rb +92 -0
- data/lib/redis_object/ext/shardable.rb +18 -0
- data/lib/redis_object/ext/triggers.rb +101 -0
- data/lib/redis_object/ext/view_caching.rb +258 -0
- data/lib/redis_object/ext/views.rb +102 -0
- data/lib/redis_object/external_index.rb +25 -0
- data/lib/redis_object/indices.rb +97 -0
- data/lib/redis_object/inheritance_tracking.rb +23 -0
- data/lib/redis_object/keys.rb +37 -0
- data/lib/redis_object/storage.rb +93 -0
- data/lib/redis_object/storage/adapter.rb +46 -0
- data/lib/redis_object/storage/aws.rb +71 -0
- data/lib/redis_object/storage/mysql.rb +47 -0
- data/lib/redis_object/storage/redis.rb +119 -0
- data/lib/redis_object/timestamps.rb +74 -0
- data/lib/redis_object/tpl.rb +17 -0
- data/lib/redis_object/types.rb +276 -0
- data/lib/redis_object/validation.rb +89 -0
- data/lib/redis_object/version.rb +5 -0
- data/redis_object.gemspec +26 -0
- data/spec/adapter_spec.rb +43 -0
- data/spec/base_spec.rb +90 -0
- data/spec/benchmark_spec.rb +46 -0
- data/spec/collections_spec.rb +144 -0
- data/spec/defaults_spec.rb +56 -0
- data/spec/filters_spec.rb +29 -0
- data/spec/indices_spec.rb +45 -0
- data/spec/rename_class_spec.rb +96 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/timestamp_spec.rb +28 -0
- data/spec/trigger_spec.rb +51 -0
- data/spec/types_spec.rb +103 -0
- data/spec/view_caching_spec.rb +130 -0
- data/spec/views_spec.rb +72 -0
- metadata +172 -0
@@ -0,0 +1,388 @@
|
|
1
|
+
module Seabright
|
2
|
+
|
3
|
+
module Collections
|
4
|
+
|
5
|
+
def hkey_col(ident = nil)
|
6
|
+
"#{hkey}:collections"
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(o_id)
|
10
|
+
super(o_id)
|
11
|
+
store.smembers(hkey_col).each do |name|
|
12
|
+
collections[name] = Seabright::Collection.load(name,self)
|
13
|
+
define_access(name) do
|
14
|
+
get_collection(name)
|
15
|
+
end
|
16
|
+
define_access(name.to_s.singularize) do
|
17
|
+
get_collection(name).latest
|
18
|
+
end
|
19
|
+
end
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_child(obj)
|
24
|
+
if col = get_collection(obj.collection_name)
|
25
|
+
col.delete obj
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def collection_name
|
30
|
+
self.class.collection_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def ref_key(ident = nil)
|
34
|
+
"#{hkey}:backreferences"
|
35
|
+
end
|
36
|
+
|
37
|
+
def reference(obj)
|
38
|
+
raise "Not an object." unless obj.is_a?(RedisObject)
|
39
|
+
get_collection(obj.collection_name) << obj.hkey
|
40
|
+
obj.referenced_by self
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(obj)
|
44
|
+
reference obj
|
45
|
+
end
|
46
|
+
|
47
|
+
def push(obj)
|
48
|
+
reference obj
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove_collection!(name)
|
52
|
+
store.srem hkey_col, name
|
53
|
+
end
|
54
|
+
|
55
|
+
def referenced_by(obj)
|
56
|
+
store.sadd(ref_key,obj.hkey)
|
57
|
+
end
|
58
|
+
|
59
|
+
def backreferences(cls = nil)
|
60
|
+
out = store.smembers(ref_key).map do |backreference_hkey|
|
61
|
+
obj = RedisObject.find_by_key(backreference_hkey)
|
62
|
+
if cls && !obj.is_a?(cls)
|
63
|
+
nil
|
64
|
+
else
|
65
|
+
obj
|
66
|
+
end
|
67
|
+
end
|
68
|
+
out.compact
|
69
|
+
end
|
70
|
+
|
71
|
+
def dereference_from(obj)
|
72
|
+
obj.get_collection(collection_name).delete(hkey)
|
73
|
+
end
|
74
|
+
|
75
|
+
def dereference_from_backreferences
|
76
|
+
backreferences.each do |backreference|
|
77
|
+
dereference_from(backreference)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get(k)
|
82
|
+
if has_collection?(k)
|
83
|
+
get_collection(k)
|
84
|
+
elsif has_collection?(pk = k.to_s.pluralize)
|
85
|
+
get_collection(pk).first
|
86
|
+
else
|
87
|
+
super(k)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_collection?(name)
|
92
|
+
collection_names.include?(name.to_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_collection(name)
|
96
|
+
if has_collection?(name)
|
97
|
+
collections[name.to_s] ||= Collection.load(name,self)
|
98
|
+
else
|
99
|
+
store.sadd hkey_col, name
|
100
|
+
@collection_names << name.to_s
|
101
|
+
collections[name.to_s] ||= Collection.load(name,self)
|
102
|
+
define_access(name.to_s.pluralize) do
|
103
|
+
get_collection(name)
|
104
|
+
end
|
105
|
+
define_access(name.to_s.singularize) do
|
106
|
+
get_collection(name).latest
|
107
|
+
end
|
108
|
+
end
|
109
|
+
collections[name.to_s]
|
110
|
+
end
|
111
|
+
|
112
|
+
def collections
|
113
|
+
@collections ||= {}
|
114
|
+
end
|
115
|
+
|
116
|
+
def collection_names
|
117
|
+
@collection_names ||= store.smembers(hkey_col)
|
118
|
+
end
|
119
|
+
|
120
|
+
def mset(dat)
|
121
|
+
dat.select! {|k,v| !collections[k.to_s] }
|
122
|
+
super(dat)
|
123
|
+
end
|
124
|
+
|
125
|
+
def set(k,v)
|
126
|
+
@data ? super(k,v) : has_collection?(k) ? get_collection(k.to_s).replace(v) : super(k,v)
|
127
|
+
v
|
128
|
+
end
|
129
|
+
|
130
|
+
module ClassMethods
|
131
|
+
|
132
|
+
def hkey_col(ident = nil)
|
133
|
+
"#{hkey(ident)}:collections"
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_child(obj)
|
137
|
+
if col = get_collection(obj.collection_name)
|
138
|
+
col.delete obj
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def collection_name
|
143
|
+
self.name.split('::').last.pluralize.underscore.to_sym
|
144
|
+
end
|
145
|
+
|
146
|
+
def reference(obj)
|
147
|
+
name = obj.collection_name
|
148
|
+
store.sadd hkey_col, name
|
149
|
+
get_collection(name) << obj.hkey
|
150
|
+
end
|
151
|
+
|
152
|
+
def <<(obj)
|
153
|
+
reference obj
|
154
|
+
end
|
155
|
+
|
156
|
+
def push(obj)
|
157
|
+
reference obj
|
158
|
+
end
|
159
|
+
|
160
|
+
def remove_collection!(name)
|
161
|
+
store.srem hkey_col, name
|
162
|
+
end
|
163
|
+
|
164
|
+
def get(k)
|
165
|
+
if has_collection?(k)
|
166
|
+
get_collection(k)
|
167
|
+
elsif has_collection?(pk = k.to_s.pluralize)
|
168
|
+
get_collection(pk).first
|
169
|
+
else
|
170
|
+
super(k)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def has_collection?(name)
|
175
|
+
store.sismember(hkey_col,name.to_s)
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_collection(name)
|
179
|
+
collections[name.to_s] ||= Collection.load(name,self)
|
180
|
+
collections[name.to_s]
|
181
|
+
end
|
182
|
+
|
183
|
+
def collections
|
184
|
+
@collections ||= {}
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.included(base)
|
190
|
+
base.extend(ClassMethods)
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
class Collection < Array
|
196
|
+
|
197
|
+
include Seabright::CachedScripts
|
198
|
+
|
199
|
+
def initialize(name,owner)
|
200
|
+
@name = name.to_s
|
201
|
+
@owner = owner
|
202
|
+
end
|
203
|
+
|
204
|
+
def remove!
|
205
|
+
@owner.remove_collection! @name
|
206
|
+
end
|
207
|
+
|
208
|
+
def latest
|
209
|
+
indexed(:created_at,5,true).first || first
|
210
|
+
end
|
211
|
+
|
212
|
+
def indexed(idx,num=-1,reverse=false)
|
213
|
+
keys = keys_by_index(idx,num,reverse)
|
214
|
+
out = Enumerator.new do |y|
|
215
|
+
keys.each do |member|
|
216
|
+
if a = class_const.find_by_key(member)
|
217
|
+
y << a
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
if block_given?
|
222
|
+
out.each do |itm|
|
223
|
+
yield itm
|
224
|
+
end
|
225
|
+
else
|
226
|
+
out
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def temp_key
|
231
|
+
"#{key}::zintersect_temp::#{RedisObject.new_id(4)}"
|
232
|
+
end
|
233
|
+
|
234
|
+
RedisObject::ScriptSources::FwdScript = "redis.call('ZINTERSTORE', KEYS[1], 2, KEYS[2], KEYS[3], 'WEIGHTS', 1, 0)\nlocal keys = redis.call('ZRANGE', KEYS[1], 0, KEYS[4])\nredis.call('DEL', KEYS[1])\nreturn keys".freeze
|
235
|
+
RedisObject::ScriptSources::RevScript = "redis.call('ZINTERSTORE', KEYS[1], 2, KEYS[2], KEYS[3], 'WEIGHTS', 1, 0)\nlocal keys = redis.call('ZREVRANGE', KEYS[1], 0, KEYS[4])\nredis.call('DEL', KEYS[1])\nreturn keys".freeze
|
236
|
+
|
237
|
+
def keys_by_index(idx,num=-1,reverse=false)
|
238
|
+
keys = run_script(reverse ? :RevScript : :FwdScript, [temp_key, index_key(idx), key, num])
|
239
|
+
Enumerator.new do |y|
|
240
|
+
keys.each do |member|
|
241
|
+
y << member
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def index_key(idx)
|
247
|
+
class_const.index_key(idx)
|
248
|
+
end
|
249
|
+
|
250
|
+
def item_key(k)
|
251
|
+
"#{class_const}:#{k}_h"
|
252
|
+
end
|
253
|
+
|
254
|
+
def find(k)
|
255
|
+
if k.is_a? String
|
256
|
+
return real_at(item_key(k))
|
257
|
+
elsif k.is_a? Hash
|
258
|
+
return match(k)
|
259
|
+
elsif k.is_a? Integer
|
260
|
+
return real_at(at(k))
|
261
|
+
end
|
262
|
+
return nil
|
263
|
+
end
|
264
|
+
|
265
|
+
def [](k)
|
266
|
+
find k
|
267
|
+
end
|
268
|
+
|
269
|
+
def match(pkt)
|
270
|
+
Enumerator.new do |y|
|
271
|
+
each do |i|
|
272
|
+
if pkt.map {|hk,va| i.get(hk)==va }.all?
|
273
|
+
y << i
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def real_at(key)
|
280
|
+
class_const.find_by_key(key)
|
281
|
+
end
|
282
|
+
|
283
|
+
def objects
|
284
|
+
each.to_a
|
285
|
+
end
|
286
|
+
|
287
|
+
def first
|
288
|
+
class_const.find_by_key(super)
|
289
|
+
end
|
290
|
+
|
291
|
+
def last
|
292
|
+
class_const.find_by_key(super)
|
293
|
+
end
|
294
|
+
|
295
|
+
def each
|
296
|
+
out = Enumerator.new do |y|
|
297
|
+
each_index do |key|
|
298
|
+
if a = class_const.find_by_key(at(key))
|
299
|
+
y << a
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
if block_given?
|
304
|
+
out.each do |a|
|
305
|
+
yield a
|
306
|
+
end
|
307
|
+
else
|
308
|
+
out
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def cleanup!
|
313
|
+
each_index do |key|
|
314
|
+
unless a = class_const.find_by_key(at(key))
|
315
|
+
puts "Deleting #{key} because not #{a.inspect}" if DEBUG
|
316
|
+
delete at(key)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
if size < 1
|
320
|
+
puts "Deleting collection #{@name} because empty" if DEBUG
|
321
|
+
remove!
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def map(&block)
|
326
|
+
each.map(&block)
|
327
|
+
end
|
328
|
+
|
329
|
+
def select(&block)
|
330
|
+
return nil unless block_given?
|
331
|
+
Enumerator.new do |y|
|
332
|
+
each_index do |key|
|
333
|
+
if (a = class_const.find_by_key(at(key))) && block.call(a)
|
334
|
+
y << a
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def delete(obj)
|
341
|
+
k = obj.class == String ? obj : obj.hkey
|
342
|
+
store.zrem(key,k)
|
343
|
+
super(k)
|
344
|
+
end
|
345
|
+
|
346
|
+
def clear!
|
347
|
+
store.zrem(key,self.join(" "))
|
348
|
+
end
|
349
|
+
|
350
|
+
def <<(obj)
|
351
|
+
k = obj.class == String ? obj : obj.hkey
|
352
|
+
store.zadd(key,store.zcount(key,"-inf", "+inf"),k)
|
353
|
+
super(k)
|
354
|
+
end
|
355
|
+
|
356
|
+
def push(obj)
|
357
|
+
self << obj
|
358
|
+
end
|
359
|
+
|
360
|
+
def class_const
|
361
|
+
self.class.class_const_for(@name)
|
362
|
+
end
|
363
|
+
|
364
|
+
def store
|
365
|
+
class_const.store
|
366
|
+
end
|
367
|
+
|
368
|
+
def key
|
369
|
+
"#{@owner ? "#{@owner.key}:" : ""}COLLECTION:#{@name}"
|
370
|
+
end
|
371
|
+
|
372
|
+
class << self
|
373
|
+
|
374
|
+
def load(name,owner)
|
375
|
+
out = new(name,owner)
|
376
|
+
out.replace class_const_for(name).store.zrange(out.key,0,-1)
|
377
|
+
out
|
378
|
+
end
|
379
|
+
|
380
|
+
def class_const_for(name)
|
381
|
+
Object.const_get(name.to_s.classify.to_sym) rescue RedisObject
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Seabright
|
2
|
+
module DefaultValues
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
def default_vals
|
7
|
+
@default_vals ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def intercept_for_defaults!
|
11
|
+
return if @intercepted_for_defaults
|
12
|
+
self.class_eval do
|
13
|
+
|
14
|
+
alias_method :undefaulted_get, :get unless method_defined?(:undefaulted_get)
|
15
|
+
def get(k)
|
16
|
+
if !is_set?(k) && (d = self.class.default_vals[k.to_sym]) && !d.nil?
|
17
|
+
return d
|
18
|
+
end
|
19
|
+
undefaulted_get(k)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
@intercepted_for_defaults = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def register_default(k,vl)
|
27
|
+
default_vals[k.to_sym] = vl
|
28
|
+
intercept_for_defaults!
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_for(k,vl)
|
32
|
+
register_default k, vl
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.included(base)
|
38
|
+
base.extend(ClassMethods)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Seabright
|
2
|
+
module History
|
3
|
+
|
4
|
+
def save_history?
|
5
|
+
save_history || self.class.save_history?
|
6
|
+
end
|
7
|
+
|
8
|
+
def store_image
|
9
|
+
store.zadd history_key, Time.now.to_i, to_json
|
10
|
+
end
|
11
|
+
|
12
|
+
def history(num=5,reverse=false)
|
13
|
+
parser = Yajl::Parser
|
14
|
+
store.send(reverse ? :zrevrange : :zrange, history_key, 0, num).collect do |member|
|
15
|
+
parser.parse(member)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def history_key(ident = nil)
|
20
|
+
"#{key}_history"
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
super
|
25
|
+
store_image if save_history?
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
def save_history!(v=true)
|
31
|
+
@@save_history = v
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_history?
|
35
|
+
@@save_history ||= false
|
36
|
+
end
|
37
|
+
|
38
|
+
def history_key(ident = id)
|
39
|
+
"#{key(ident)}_history"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.included(base)
|
45
|
+
base.extend(ClassMethods)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|