redisted 0.0.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.
- 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
|