redisted 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +16 -0
- data/Guardfile +31 -0
- data/README.md +643 -0
- data/Rakefile +1 -0
- data/config/application.rb +59 -0
- data/config/boot.rb +6 -0
- data/config/environment.rb +7 -0
- data/config/redis.rb +17 -0
- data/config/redis.yml +0 -0
- data/index.html +1 -0
- data/lib/redisted/base.rb +129 -0
- data/lib/redisted/delete.rb +21 -0
- data/lib/redisted/errors.rb +12 -0
- data/lib/redisted/fields.rb +59 -0
- data/lib/redisted/getsetattr.rb +220 -0
- data/lib/redisted/index.rb +330 -0
- data/lib/redisted/instantiate.rb +34 -0
- data/lib/redisted/references.rb +105 -0
- data/lib/redisted/relation.rb +190 -0
- data/lib/redisted/version.rb +3 -0
- data/lib/redisted.rb +10 -0
- data/log/development.log +0 -0
- data/log/test.log +0 -0
- data/readme.md +643 -0
- data/redisted.gemspec +23 -0
- data/spec/redisted/callbacks_spec.rb +76 -0
- data/spec/redisted/delete_destroy_spec.rb +49 -0
- data/spec/redisted/fields_spec.rb +118 -0
- data/spec/redisted/filter_index_spec.rb +90 -0
- data/spec/redisted/find_spec.rb +30 -0
- data/spec/redisted/log/test.log +0 -0
- data/spec/redisted/match_index_spec.rb +36 -0
- data/spec/redisted/persisted_fields_spec.rb +238 -0
- data/spec/redisted/recalculate_index_spec.rb +6 -0
- data/spec/redisted/references_spec.rb +128 -0
- data/spec/redisted/scopes_spec.rb +88 -0
- data/spec/redisted/unique_index_spec.rb +113 -0
- data/spec/redisted/validations_spec.rb +71 -0
- data/spec/spec_helper.rb +61 -0
- metadata +110 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# Uniqueness Index:
|
4
|
+
#
|
5
|
+
# index :test_int, unique: true
|
6
|
+
# >> An index named "test_int" that verifies that "test_int" is always unique.
|
7
|
+
# index :ti, unique: :test_int
|
8
|
+
# >> An index named "ti" that verifies that "test_int" is always unique.
|
9
|
+
# index :idxkey, unique: [:test_int,:provider]
|
10
|
+
# >> An index named "idxkey" that verifies that the "test_int"/"provider" pair is always unique.
|
11
|
+
#
|
12
|
+
# Filter/Sort Index:
|
13
|
+
#
|
14
|
+
# index :odd,includes: ->(elem){(elem.test_int%2)!=0}
|
15
|
+
# >> An index named "odd" that contains reference to objects who's test_int values are odd numbers.
|
16
|
+
# index :asc,order: ->(elem){elem.test_int},fields: :test_int
|
17
|
+
# >> An index named "asc" that contains references to *all* objects, sorted by test_int. (only recalculate when :test_int changes)
|
18
|
+
# index :sortedodd,includes: ->(elem){(elem.test_int%2)!=0}, order: ->(elem){elem.test_int}
|
19
|
+
# >> An index named "sortedodd" that contains reference to objects who's test_int values are odd numbers, sorted by test_int
|
20
|
+
# index :provider, match: ->(elem){(elem.provider)}
|
21
|
+
# >> Create multiple indices, one for each unique value returned by the match proc. Used for filtering.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
module Redisted
|
25
|
+
class Base
|
26
|
+
private
|
27
|
+
def init_indexes
|
28
|
+
end
|
29
|
+
public
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# Setup
|
33
|
+
#
|
34
|
+
#
|
35
|
+
def indices
|
36
|
+
self.class.indices
|
37
|
+
end
|
38
|
+
class << self
|
39
|
+
def indices= val
|
40
|
+
@indices=val
|
41
|
+
end
|
42
|
+
def indices
|
43
|
+
@indices||={}
|
44
|
+
@indices
|
45
|
+
end
|
46
|
+
def index name,params
|
47
|
+
raise InvalidIndex,"Index redefined: #{name}" if indices[name]
|
48
|
+
entry={
|
49
|
+
redis_key: "#{prefix}[#{name}]",
|
50
|
+
optimistic_retry: params[:optimistic_retry],
|
51
|
+
}
|
52
|
+
if params[:unique]
|
53
|
+
entry[:unique_index]=true
|
54
|
+
entry[:fields]= if params[:unique].is_a? Array
|
55
|
+
params[:unique]
|
56
|
+
elsif params[:unique].is_a? Symbol
|
57
|
+
[params[:unique]]
|
58
|
+
else
|
59
|
+
[name]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
entry[:unique_index]=false
|
63
|
+
entry[:includes]=lambdafi_index(params[:includes],true)
|
64
|
+
entry[:order]=lambdafi_index(params[:order],1)
|
65
|
+
entry[:match]=lambdafi_index(params[:match],"") if params[:match]
|
66
|
+
entry[:fields]= if params[:fields].nil?
|
67
|
+
nil
|
68
|
+
elsif params[:fields].is_a? Array
|
69
|
+
params[:fields]
|
70
|
+
elsif params[:fields].is_a? Symbol
|
71
|
+
[params[:fields]]
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
indices[name]=entry
|
77
|
+
end
|
78
|
+
def lambdafi_index options,default_value
|
79
|
+
lambda do |*args|
|
80
|
+
ret=nil
|
81
|
+
if options.nil?
|
82
|
+
ret=default_value
|
83
|
+
elsif options.respond_to? :call
|
84
|
+
ret=options.call(*args)
|
85
|
+
elsif options.is_a? Symbol
|
86
|
+
ret=args[0].send(options,*args)
|
87
|
+
else
|
88
|
+
ret=options # An integer or other constant value...
|
89
|
+
end
|
90
|
+
ret
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
#
|
98
|
+
#
|
99
|
+
# Process Index:
|
100
|
+
#
|
101
|
+
# data is a hash of name/value pairs of fields that are going to be changed. The
|
102
|
+
# key is the field name, and the value is the *new*, to be assigned, value of the
|
103
|
+
# field (not the current value).
|
104
|
+
# If the old value is needed, it must be pulled from @attributes_old. If it isn't
|
105
|
+
# in @attributes_old, then it must be read from Redis *before* proc.call is made.
|
106
|
+
# proc.call should make *set redis calls to update the proper redis keys to implement
|
107
|
+
# the desired results. It *cannot* make *get redis calls or any other redis calls that
|
108
|
+
# require a return value to be correct (this is because this is executed within a multi).
|
109
|
+
# Additionally, @attributes_value must be updated to the new value in this call.
|
110
|
+
#
|
111
|
+
#
|
112
|
+
# TODO: Needs repeat logic for optimistic locking...
|
113
|
+
def index_process data,&proc
|
114
|
+
state={data: data}
|
115
|
+
names=[]
|
116
|
+
data.each do |name,value|
|
117
|
+
names<<name
|
118
|
+
end
|
119
|
+
state[:attr_names]=names
|
120
|
+
u=uniq_index_start state
|
121
|
+
i=index_start state
|
122
|
+
@watch_list=[]
|
123
|
+
u=uniq_index_watch_setup u
|
124
|
+
i=index_watch_setup i
|
125
|
+
begin
|
126
|
+
if @watch_list.size>0
|
127
|
+
@redis.unwatch
|
128
|
+
@redis.watch *@watch_list
|
129
|
+
end
|
130
|
+
u=uniq_index_verify u
|
131
|
+
i=index_verify i
|
132
|
+
@redis.multi
|
133
|
+
begin
|
134
|
+
u=uniq_index_multi_start u
|
135
|
+
i=index_multi_start i
|
136
|
+
proc.call
|
137
|
+
u=uniq_index_multi_end u
|
138
|
+
i=index_multi_end i
|
139
|
+
@redis.exec
|
140
|
+
rescue Exception
|
141
|
+
@redis.discard
|
142
|
+
raise
|
143
|
+
end
|
144
|
+
rescue Exception
|
145
|
+
@redis.unwatch if @watch_list.size>0
|
146
|
+
raise
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
#
|
152
|
+
#
|
153
|
+
# Unique Index
|
154
|
+
#
|
155
|
+
#
|
156
|
+
# Creates Key:
|
157
|
+
#
|
158
|
+
# SET: model[index_name]
|
159
|
+
#
|
160
|
+
# Contains all id values from all instances of this model, and is used to verify uniqueness.
|
161
|
+
# Maintained during set_attr/save.
|
162
|
+
#
|
163
|
+
#
|
164
|
+
#
|
165
|
+
def uniq_index_start state
|
166
|
+
state
|
167
|
+
end
|
168
|
+
def uniq_index_watch_setup state
|
169
|
+
# Begin optimistic locking for all indices that are impacted by these changes...
|
170
|
+
indices.each do |name,index|
|
171
|
+
next if !index[:unique_index]
|
172
|
+
next if (index[:fields] & state[:attr_names]).size==0
|
173
|
+
@watch_list << index[:redis_key]
|
174
|
+
end
|
175
|
+
state
|
176
|
+
end
|
177
|
+
|
178
|
+
def uniq_index_verify state
|
179
|
+
indices.each do |idxname,index|
|
180
|
+
next if !index[:unique_index]
|
181
|
+
index[:fields].each do |fieldname|
|
182
|
+
# if we don't have it, we need to get it now...directly from redis
|
183
|
+
if @attributes_old_status[fieldname]!=:cached
|
184
|
+
@attributes_old[fieldname]=to_attr_type fieldname,@redis.hget("#{prefix}:#{@id}",fieldname)
|
185
|
+
@attributes_old_status[fieldname]=:cached
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
cmds_to_do={}
|
191
|
+
indices.each do |name,index|
|
192
|
+
next if !index[:unique_index]
|
193
|
+
next if (index[:fields] & state[:attr_names]).size==0
|
194
|
+
# Need to get the old and new index value...
|
195
|
+
old=unique_index_value(@attributes_old,index[:fields])
|
196
|
+
new=unique_index_value(@attributes_value.merge(state[:data]),index[:fields])
|
197
|
+
next if old==new
|
198
|
+
cmds_to_do[index[:redis_key]]||={old_vals: [],new_vals: []}
|
199
|
+
cmds_to_do[index[:redis_key]][:old_vals] << old if !old.nil?
|
200
|
+
cmds_to_do[index[:redis_key]][:new_vals] << new if !new.nil?
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Verify no value in the "new" list that is not in the "old" list actually exists in the index...
|
205
|
+
#
|
206
|
+
cmds_to_do.each do |redis_key,cmds|
|
207
|
+
(cmds[:new_vals]-cmds[:old_vals]).each do |chk_val|
|
208
|
+
raise ValueNotUnique, "Value #{chk_val} is not unique" if @redis.sismember(redis_key,chk_val)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
state[:cmds_to_do]=cmds_to_do
|
213
|
+
state
|
214
|
+
end
|
215
|
+
def uniq_index_multi_start state
|
216
|
+
state
|
217
|
+
end
|
218
|
+
def uniq_index_multi_end state
|
219
|
+
state[:cmds_to_do].each do |redis_key,cmds|
|
220
|
+
@redis.srem(redis_key,*cmds[:old_vals]) if cmds[:old_vals].size>0
|
221
|
+
@redis.sadd(redis_key,*cmds[:new_vals]) if cmds[:new_vals].size>0
|
222
|
+
end
|
223
|
+
state
|
224
|
+
end
|
225
|
+
def unique_index_value vals,fields
|
226
|
+
ret=[]
|
227
|
+
fields.each do |field|
|
228
|
+
return nil if vals[field].nil? # If any value is nil, then the whole index is nil...
|
229
|
+
ret<<to_redis_type(field,vals[field]) # TODO: Take out ':','%' from the string...encode them instead...
|
230
|
+
end
|
231
|
+
ret.join(':')
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
#
|
236
|
+
#
|
237
|
+
# Filter/Sort Index
|
238
|
+
#
|
239
|
+
#
|
240
|
+
# Creates Key:
|
241
|
+
#
|
242
|
+
# SORTED SET: model[index_name]
|
243
|
+
#
|
244
|
+
# Contains all id values from instances of this model where the ":includes" proc returns true.
|
245
|
+
# If no ":includes" proc is given, then all instances are included. The score for all entries
|
246
|
+
# is the value returned by the ":order" proc. If that proc is not specified, then the score
|
247
|
+
# will all be 1.
|
248
|
+
#
|
249
|
+
#
|
250
|
+
#
|
251
|
+
def index_start state
|
252
|
+
state
|
253
|
+
end
|
254
|
+
def index_watch_setup state
|
255
|
+
indices.each do |name,index|
|
256
|
+
next if index[:unique_index]
|
257
|
+
next if (!index[:fields].nil?) and ((index[:fields] & state[:attr_names]).size==0)
|
258
|
+
@watch_list << index[:redis_key]
|
259
|
+
end
|
260
|
+
attr_old={}
|
261
|
+
attr_old_status={}
|
262
|
+
indices.each do |name,index|
|
263
|
+
next if index[:unique_index]
|
264
|
+
# See if we have a "match" field...if so, we need to cache the old values
|
265
|
+
if index[:match]
|
266
|
+
index[:fields].each do |fieldname|
|
267
|
+
# if we don't have it, we need to get it now...directly from redis
|
268
|
+
if attr_old_status[fieldname]!=:cached
|
269
|
+
attr_old[fieldname]=to_attr_type fieldname,@redis.hget("#{prefix}:#{@id}",fieldname)
|
270
|
+
attr_old_status[fieldname]=:cached
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
state[:attributes_old]=attr_old
|
276
|
+
state[:attributes_old_status]=attr_old_status
|
277
|
+
state
|
278
|
+
end
|
279
|
+
|
280
|
+
def index_verify state
|
281
|
+
state
|
282
|
+
end
|
283
|
+
def index_multi_start state
|
284
|
+
state
|
285
|
+
end
|
286
|
+
def index_multi_end state
|
287
|
+
indices.each do |name,index|
|
288
|
+
next if index[:unique_index]
|
289
|
+
next if (!index[:fields].nil?) and ((index[:fields] & state[:attr_names]).size==0)
|
290
|
+
|
291
|
+
if index[:match].nil?
|
292
|
+
if index[:includes].call(self)
|
293
|
+
@redis.zadd index[:redis_key],index[:order].call(self),self.id
|
294
|
+
else
|
295
|
+
@redis.zrem index[:redis_key],self.id
|
296
|
+
end
|
297
|
+
else
|
298
|
+
old=self.dup
|
299
|
+
old.internal_only_force_update_attributes(state[:attributes_old],state[:attributes_old_status])
|
300
|
+
old_match_val=index[:match].call(old)
|
301
|
+
new_match_val=index[:match].call(self)
|
302
|
+
|
303
|
+
|
304
|
+
if !old_match_val.nil? and old_match_val!=""
|
305
|
+
@redis.zrem "#{index[:redis_key]}:#{old_match_val}",self.id
|
306
|
+
end
|
307
|
+
if !new_match_val.nil? and new_match_val!=""
|
308
|
+
if index[:includes].call(self)
|
309
|
+
@redis.zadd "#{index[:redis_key]}:#{new_match_val}",index[:order].call(self),self.id
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
end
|
320
|
+
state
|
321
|
+
end
|
322
|
+
|
323
|
+
public # Unfortunately, this must be public, even though it should never be called externally
|
324
|
+
def internal_only_force_update_attributes attr,status
|
325
|
+
@attributes_value=attr
|
326
|
+
@attributes_status=status
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
#
|
5
|
+
# Use
|
6
|
+
#
|
7
|
+
def create attrs=nil,redis=nil
|
8
|
+
ret=new attrs,redis
|
9
|
+
ret.save
|
10
|
+
ret
|
11
|
+
end
|
12
|
+
def find ids
|
13
|
+
if ids.is_a? Array
|
14
|
+
ret=[]
|
15
|
+
ids.each do |id|
|
16
|
+
m=new
|
17
|
+
m.load id
|
18
|
+
ret << m
|
19
|
+
end
|
20
|
+
ret
|
21
|
+
else
|
22
|
+
m=new
|
23
|
+
m.load ids
|
24
|
+
m
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def load id
|
29
|
+
@id=id
|
30
|
+
pre_cache_values if get_obj_option(:pre_cache_all) and get_obj_option(:pre_cache_all)[:when]==:create
|
31
|
+
self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
def references
|
4
|
+
self.class.references
|
5
|
+
end
|
6
|
+
class << self
|
7
|
+
def references= val
|
8
|
+
@references=val
|
9
|
+
end
|
10
|
+
def references
|
11
|
+
@references||={}
|
12
|
+
@references
|
13
|
+
end
|
14
|
+
end
|
15
|
+
private
|
16
|
+
def init_references
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_reference ref,details
|
20
|
+
ret=nil
|
21
|
+
# TODO: Can this be cached???
|
22
|
+
if details[:ref_type]==:one
|
23
|
+
id=get_attr("#{details[:string]}_id".to_sym)
|
24
|
+
ret=details[:class].find(id)
|
25
|
+
elsif details[:ref_type]==:many
|
26
|
+
if details[:model_type]==:redisted
|
27
|
+
ret=details[:class].scoped
|
28
|
+
ret.internal_add_reference "#{prefix}[#{details[:string]}_id]"
|
29
|
+
else
|
30
|
+
return [] if @redis.exists "#{prefix}#[{details[:string]}_id]"
|
31
|
+
id_list=@redis.zrange "#{prefix}[#{details[:string]}_id]",0,-1
|
32
|
+
ret=details[:class].find(id_list)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
ret
|
36
|
+
end
|
37
|
+
def set_reference ref,details,val
|
38
|
+
if details[:ref_type]==:one
|
39
|
+
set_attr "#{details[:string]}_id".to_sym,val.id.to_s
|
40
|
+
elsif details[:ref_type]==:many
|
41
|
+
raise "LEELEE: Not implemented yet"
|
42
|
+
end
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
class << self
|
46
|
+
public
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
#
|
51
|
+
# Setup
|
52
|
+
#
|
53
|
+
def references_one sym,opt={}
|
54
|
+
details=name_to_class_details sym,opt[:as]
|
55
|
+
raise InvalidReference,"Reference already defined" if !references[details[:symbol]].nil?
|
56
|
+
field "#{details[:string]}_id".to_sym
|
57
|
+
details[:ref_type]=:one
|
58
|
+
references[details[:symbol]]=details
|
59
|
+
end
|
60
|
+
def references_many sym,opt={}
|
61
|
+
details=name_to_class_details sym,opt[:as]
|
62
|
+
raise InvalidReference,"Reference already defined" if !references[details[:plural][:symbol]].nil?
|
63
|
+
field "#{details[:plural][:string]}_list".to_sym
|
64
|
+
details[:ref_type]=:many
|
65
|
+
references[details[:plural][:symbol]]=details
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def name_to_class_details obj,alt_name
|
71
|
+
if alt_name
|
72
|
+
ret={
|
73
|
+
symbol: alt_name.to_sym,
|
74
|
+
string: alt_name.to_s,
|
75
|
+
plural:{
|
76
|
+
symbol: alt_name.to_sym,
|
77
|
+
string: alt_name.to_s,
|
78
|
+
}
|
79
|
+
}
|
80
|
+
else
|
81
|
+
ret={
|
82
|
+
symbol: obj.to_sym,
|
83
|
+
string: obj.to_s,
|
84
|
+
plural:{
|
85
|
+
symbol: obj.to_s.pluralize.to_sym,
|
86
|
+
string: obj.to_s.pluralize
|
87
|
+
}
|
88
|
+
}
|
89
|
+
end
|
90
|
+
ret[:class]=if obj.kind_of? Symbol
|
91
|
+
Kernel.const_get obj.to_s.capitalize
|
92
|
+
else
|
93
|
+
Kernel.const_get obj
|
94
|
+
end
|
95
|
+
if ret[:class].respond_to?("is_redisted_model?") and ret[:class].is_redisted_model?
|
96
|
+
ret[:model_type]=:redisted
|
97
|
+
else
|
98
|
+
ret[:model_type]=:other
|
99
|
+
end
|
100
|
+
ret
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Redisted
|
2
|
+
class Base
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# Relation
|
6
|
+
#
|
7
|
+
#
|
8
|
+
class Relation
|
9
|
+
def initialize the_class,redis,scopes,indices
|
10
|
+
@the_class=the_class
|
11
|
+
@redis=redis
|
12
|
+
@scopes=scopes
|
13
|
+
@indices=indices
|
14
|
+
@is_in_list=[]
|
15
|
+
end
|
16
|
+
#
|
17
|
+
# Low Level Setup Conditions
|
18
|
+
#
|
19
|
+
def is_in index_name,*args
|
20
|
+
index=@indices[index_name.to_sym]
|
21
|
+
raise InvalidQuery,"Could not find index '#{index_name}'" if index.nil?
|
22
|
+
if index[:match]
|
23
|
+
raise InvalidQuery,"No parameter provided to index when one was expected" if args.size!=1
|
24
|
+
else
|
25
|
+
raise InvalidQuery,"Parameter provided to index when not expected" if args.size!=0
|
26
|
+
end
|
27
|
+
@is_in_list<<{:index_name=>index_name,:index=>index,:args=>args}
|
28
|
+
self
|
29
|
+
end
|
30
|
+
def internal_add_reference redis_key
|
31
|
+
@is_in_list<<{
|
32
|
+
:index_name=>nil,
|
33
|
+
:args=>nil,
|
34
|
+
:index=>{
|
35
|
+
:redis_key=>redis_key
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
#
|
43
|
+
# Calculate Results
|
44
|
+
#
|
45
|
+
def first
|
46
|
+
key=merge_keys @is_in_list
|
47
|
+
id=nil
|
48
|
+
if key
|
49
|
+
id_list=@redis.zrange key,0,-1 # LEELEE: Performance improve...
|
50
|
+
raise InvalidQuery,"No keys" if id_list.size==0
|
51
|
+
id=id_list.first
|
52
|
+
else
|
53
|
+
keyprefix="#{prefix}:"
|
54
|
+
key_list=@redis.keys "#{keyprefix}*"
|
55
|
+
raise InvalidQuery,"No keys" if key_list.size==0
|
56
|
+
id=id_list.first[keyprefix.size..-1]
|
57
|
+
end
|
58
|
+
obj=@the_class.new
|
59
|
+
obj.load id
|
60
|
+
obj
|
61
|
+
end
|
62
|
+
def last
|
63
|
+
key=merge_keys @is_in_list
|
64
|
+
id=nil
|
65
|
+
if key
|
66
|
+
id_list=@redis.zrange key,0,-1 # LEELEE: Performance improve...
|
67
|
+
raise InvalidQuery,"No keys" if id_list.size==0
|
68
|
+
id=id_list.last
|
69
|
+
else
|
70
|
+
keyprefix="#{prefix}:"
|
71
|
+
key_list=@redis.keys "#{keyprefix}*"
|
72
|
+
raise InvalidQuery,"No keys" if key_list.size==0
|
73
|
+
id=id_list.last[keyprefix.size..-1]
|
74
|
+
end
|
75
|
+
obj=@the_class.new
|
76
|
+
obj.load id
|
77
|
+
obj
|
78
|
+
end
|
79
|
+
def all
|
80
|
+
ret=[]
|
81
|
+
key=merge_keys @is_in_list
|
82
|
+
if key
|
83
|
+
id_list=@redis.zrange key,0,-1
|
84
|
+
id_list.each do |id|
|
85
|
+
obj=@the_class.new
|
86
|
+
obj.load id
|
87
|
+
ret<<obj
|
88
|
+
end
|
89
|
+
else
|
90
|
+
keyprefix="#{prefix}:"
|
91
|
+
key_list=@redis.keys "#{keyprefix}*"
|
92
|
+
key_list.each do |key|
|
93
|
+
obj=@the_class.new
|
94
|
+
obj.load key[keyprefix.size..-1]
|
95
|
+
ret<<obj
|
96
|
+
end
|
97
|
+
end
|
98
|
+
ret
|
99
|
+
end
|
100
|
+
def each &proc
|
101
|
+
key=merge_keys @is_in_list
|
102
|
+
if key
|
103
|
+
id_list=@redis.zrange key,0,-1
|
104
|
+
id_list.each do |id|
|
105
|
+
obj=@the_class.new
|
106
|
+
obj.load id
|
107
|
+
proc.call(obj)
|
108
|
+
end
|
109
|
+
else
|
110
|
+
keyprefix="#{prefix}:"
|
111
|
+
key_list=@redis.keys "#{keyprefix}*"
|
112
|
+
key_list.each do |key|
|
113
|
+
obj=@the_class.new
|
114
|
+
obj.load key[keyprefix.size..-1]
|
115
|
+
proc.call(obj)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
def delete_all
|
121
|
+
# TODO: Don't instantiate the object for delete...do it manually...
|
122
|
+
#self.each do |m|
|
123
|
+
# m.delete
|
124
|
+
#end
|
125
|
+
end
|
126
|
+
def destroy_all
|
127
|
+
self.each do |m|
|
128
|
+
m.destroy
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def method_missing(id, *args)
|
132
|
+
return add_one args[0] if id.to_s=="<<"
|
133
|
+
return @scopes[id].call(*args) if @scopes[id]
|
134
|
+
return is_in id[3..-1],*args if id[0,3]=="by_"
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def prefix
|
140
|
+
@the_class.prefix
|
141
|
+
end
|
142
|
+
def merge_keys list
|
143
|
+
return nil if list.size==0
|
144
|
+
return key_name(list.first) if list.size==1
|
145
|
+
cnt=@redis.incr "redisted_tmp_id"
|
146
|
+
# TODO: Handle calculating if weights should be used...
|
147
|
+
tmpkey="redisted_tmp[#{cnt}]"
|
148
|
+
key_list=[]
|
149
|
+
@is_in_list.each do |index|
|
150
|
+
key_list<<key_name(index)
|
151
|
+
end
|
152
|
+
@redis.zinterstore tmpkey,key_list,{:aggregate=>:sum,:weight=>3}
|
153
|
+
@redis.expire tmpkey,3600 # TODO: Value should be configurable...
|
154
|
+
return tmpkey
|
155
|
+
end
|
156
|
+
def key_name index
|
157
|
+
ret=index[:index][:redis_key]
|
158
|
+
ret+=":#{index[:args][0]}" if index[:index][:match]
|
159
|
+
ret
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
#
|
164
|
+
# Add objects to a relation
|
165
|
+
#
|
166
|
+
#
|
167
|
+
def add_one val
|
168
|
+
@is_in_list.each do |index|
|
169
|
+
next if !index[:index_name].nil?
|
170
|
+
@redis.zadd index[:index][:redis_key],1,val.id
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
class << self
|
177
|
+
def scoped
|
178
|
+
Relation.new self,@@redis,scopes,indices
|
179
|
+
end
|
180
|
+
def method_missing(id,*args,&proc)
|
181
|
+
is_scoped=(id[0]=='_')
|
182
|
+
is_scoped||=[:first,:last,:all,:each,:delete_all,:destroy_all,:where,:limit,:offset,:order,:reverse_order].include? id.to_sym
|
183
|
+
is_scoped||= !scopes[id.to_sym].nil?
|
184
|
+
is_scoped||= (id[0,3]=="by_")
|
185
|
+
return scoped.send(id,*args,&proc) if is_scoped
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/lib/redisted.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "redisted/version"
|
2
|
+
require "redisted/errors"
|
3
|
+
require "redisted/base"
|
4
|
+
require "redisted/fields"
|
5
|
+
require "redisted/instantiate"
|
6
|
+
require "redisted/getsetattr"
|
7
|
+
require "redisted/index"
|
8
|
+
require "redisted/relation"
|
9
|
+
require "redisted/references"
|
10
|
+
require "redisted/delete"
|
data/log/development.log
ADDED
File without changes
|
data/log/test.log
ADDED
File without changes
|