zermelo 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +76 -52
- data/lib/zermelo/associations/association_data.rb +4 -3
- data/lib/zermelo/associations/class_methods.rb +37 -50
- data/lib/zermelo/associations/index.rb +3 -1
- data/lib/zermelo/associations/multiple.rb +247 -0
- data/lib/zermelo/associations/range_index.rb +44 -0
- data/lib/zermelo/associations/singular.rb +193 -0
- data/lib/zermelo/associations/unique_index.rb +4 -3
- data/lib/zermelo/backend.rb +120 -0
- data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
- data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
- data/lib/zermelo/backends/stub.rb +43 -0
- data/lib/zermelo/filter.rb +194 -0
- data/lib/zermelo/filters/index_range.rb +22 -0
- data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
- data/lib/zermelo/filters/redis.rb +173 -0
- data/lib/zermelo/filters/steps/list_step.rb +48 -30
- data/lib/zermelo/filters/steps/set_step.rb +148 -89
- data/lib/zermelo/filters/steps/sort_step.rb +2 -2
- data/lib/zermelo/record.rb +53 -0
- data/lib/zermelo/records/attributes.rb +32 -0
- data/lib/zermelo/records/class_methods.rb +12 -25
- data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
- data/lib/zermelo/records/instance_methods.rb +9 -8
- data/lib/zermelo/records/key.rb +3 -1
- data/lib/zermelo/records/redis.rb +17 -0
- data/lib/zermelo/records/stub.rb +17 -0
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/index_spec.rb +70 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
- data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
- data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
- data/spec/lib/zermelo/filter_spec.rb +363 -0
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
- data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mock_logger.rb +48 -0
- metadata +31 -46
- data/lib/zermelo/associations/belongs_to.rb +0 -115
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
- data/lib/zermelo/associations/has_many.rb +0 -120
- data/lib/zermelo/associations/has_one.rb +0 -109
- data/lib/zermelo/associations/has_sorted_set.rb +0 -124
- data/lib/zermelo/backends/base.rb +0 -115
- data/lib/zermelo/filters/base.rb +0 -212
- data/lib/zermelo/filters/redis_filter.rb +0 -111
- data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
- data/lib/zermelo/records/base.rb +0 -62
- data/lib/zermelo/records/redis_record.rb +0 -27
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
- data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
- data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
- data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
- data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
- data/spec/lib/zermelo/records/key_spec.rb +0 -6
- data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
- data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
- data/spec/lib/zermelo/version_spec.rb +0 -6
- data/spec/lib/zermelo_spec.rb +0 -6
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'zermelo/
|
1
|
+
require 'zermelo/backend'
|
2
2
|
|
3
|
-
require 'zermelo/filters/
|
3
|
+
require 'zermelo/filters/influxdb'
|
4
4
|
|
5
5
|
# NB influxdb doesn't support individually addressable deletes, so
|
6
6
|
# this backend only works to write new records
|
@@ -11,16 +11,19 @@ module Zermelo
|
|
11
11
|
|
12
12
|
module Backends
|
13
13
|
|
14
|
-
class
|
14
|
+
class InfluxDB
|
15
15
|
|
16
|
-
include Zermelo::
|
16
|
+
include Zermelo::Backend
|
17
17
|
|
18
|
-
def default_sorted_set_key
|
19
|
-
|
20
|
-
end
|
18
|
+
# def default_sorted_set_key
|
19
|
+
# :time
|
20
|
+
# end
|
21
|
+
|
22
|
+
def filter(ids_key, associated_class, callback_target_class = nil,
|
23
|
+
callback_target_id = nil, callbacks = nil, sort_order = nil)
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
Zermelo::Filters::InfluxDB.new(self, ids_key, associated_class,
|
26
|
+
callback_target_class, callback_target_id, callbacks, sort_order)
|
24
27
|
end
|
25
28
|
|
26
29
|
# TODO get filter calling this instead of using same logic
|
@@ -38,7 +41,7 @@ module Zermelo
|
|
38
41
|
begin
|
39
42
|
records = Zermelo.influxdb.query("SELECT #{attr_key.name} FROM " +
|
40
43
|
"\"#{class_key}/#{attr_key.id}\" LIMIT 1")["#{class_key}/#{attr_key.id}"]
|
41
|
-
rescue InfluxDB::Error => ide
|
44
|
+
rescue ::InfluxDB::Error => ide
|
42
45
|
raise unless
|
43
46
|
/^Field #{attr_key.name} doesn't exist in series #{class_key}\/#{attr_key.id}$/ === ide.message
|
44
47
|
|
@@ -50,9 +53,16 @@ module Zermelo
|
|
50
53
|
memo[class_key][attr_key.id] ||= {}
|
51
54
|
|
52
55
|
memo[class_key][attr_key.id][attr_key.name.to_s] = if value.nil?
|
53
|
-
|
56
|
+
case attr_key.type
|
57
|
+
when :list
|
58
|
+
[]
|
59
|
+
when :hash
|
60
|
+
{}
|
61
|
+
when :set
|
62
|
+
Set.new()
|
63
|
+
else nil
|
64
|
+
end
|
54
65
|
else
|
55
|
-
|
56
66
|
case attr_key.type
|
57
67
|
when :string
|
58
68
|
value.to_s
|
@@ -130,11 +140,29 @@ module Zermelo
|
|
130
140
|
class_key = key.klass.send(:class_key)
|
131
141
|
|
132
142
|
records[class_key] ||= {}
|
133
|
-
records[class_key][key.id] ||= {}
|
134
143
|
|
135
|
-
records[class_key][key.id]
|
144
|
+
if records[class_key][key.id].nil?
|
145
|
+
begin
|
146
|
+
result = Zermelo.influxdb.query("SELECT * FROM \"#{class_key}/#{key.id}\" LIMIT 1")["#{class_key}/#{key.id}"]
|
147
|
+
if result.nil?
|
148
|
+
records[class_key][key.id] = {}
|
149
|
+
else
|
150
|
+
records[class_key][key.id] = result.first
|
151
|
+
records[class_key][key.id].delete_if {|k,v| ["time", "sequence_number"].include?(k) }
|
152
|
+
end
|
153
|
+
rescue ::InfluxDB::Error => ide
|
154
|
+
raise unless
|
155
|
+
(/^Couldn't look up columns for series: #{class_key}\/#{key.id}$/ === ide.message) ||
|
156
|
+
(/^Couldn't look up columns$/ === ide.message) ||
|
157
|
+
(/^Couldn't find series: #{class_key}\/#{key.id}$/ === ide.message)
|
158
|
+
|
159
|
+
records[class_key][key.id] = {}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
case op
|
136
164
|
when :set
|
137
|
-
case key.type
|
165
|
+
records[class_key][key.id][key.name] = case key.type
|
138
166
|
when :string, :integer
|
139
167
|
value.nil? ? nil : value.to_s
|
140
168
|
when :timestamp
|
@@ -148,12 +176,51 @@ module Zermelo
|
|
148
176
|
end
|
149
177
|
when :add
|
150
178
|
case key.type
|
151
|
-
when :
|
152
|
-
|
179
|
+
when :hash
|
180
|
+
if records[class_key][key.id][key.name].nil?
|
181
|
+
records[class_key][key.id][key.name] = value
|
182
|
+
else
|
183
|
+
records[class_key][key.id][key.name].update(value)
|
184
|
+
end
|
185
|
+
when :list
|
186
|
+
if records[class_key][key.id][key.name].nil?
|
187
|
+
records[class_key][key.id][key.name] = value
|
188
|
+
else
|
189
|
+
records[class_key][key.id][key.name] += value
|
190
|
+
end
|
153
191
|
when :set
|
154
|
-
value.to_a
|
192
|
+
v = value.to_a
|
193
|
+
if records[class_key][key.id][key.name].nil?
|
194
|
+
records[class_key][key.id][key.name] = v
|
195
|
+
else
|
196
|
+
records[class_key][key.id][key.name] += v
|
197
|
+
end
|
198
|
+
when :sorted_set
|
199
|
+
v = (1...value.size).step(2).collect {|i| value[i] }
|
200
|
+
if records[class_key][key.id][key.name].nil?
|
201
|
+
records[class_key][key.id][key.name] = v
|
202
|
+
else
|
203
|
+
records[class_key][key.id][key.name] += v
|
204
|
+
end
|
205
|
+
end
|
206
|
+
when :delete
|
207
|
+
case key.type
|
208
|
+
when :hash
|
209
|
+
unless records[class_key][key.id][key.name].nil?
|
210
|
+
# FIXME
|
211
|
+
end
|
212
|
+
when :list
|
213
|
+
unless records[class_key][key.id][key.name].nil?
|
214
|
+
records[class_key][key.id][key.name] -= value
|
215
|
+
end
|
216
|
+
when :set
|
217
|
+
unless records[class_key][key.id][key.name].nil?
|
218
|
+
records[class_key][key.id][key.name] -= value.to_a
|
219
|
+
end
|
155
220
|
when :sorted_set
|
156
|
-
|
221
|
+
unless records[class_key][key.id][key.name].nil?
|
222
|
+
records[class_key][key.id][key.name] -= (1...value.size).step(2).collect {|i| value[i] }
|
223
|
+
end
|
157
224
|
end
|
158
225
|
when :purge
|
159
226
|
purges << "\"#{class_key}/#{key.id}\""
|
@@ -163,19 +230,8 @@ module Zermelo
|
|
163
230
|
|
164
231
|
records.each_pair do |class_key, klass_records|
|
165
232
|
klass_records.each_pair do |id, data|
|
166
|
-
begin
|
167
|
-
prior = Zermelo.influxdb.query("SELECT * FROM \"#{class_key}/#{id}\" LIMIT 1")["#{class_key}/#{id}"]
|
168
|
-
rescue InfluxDB::Error => ide
|
169
|
-
raise unless
|
170
|
-
(/^Couldn't look up columns for series: #{class_key}\/#{id}$/ === ide.message) ||
|
171
|
-
(/^Couldn't look up columns$/ === ide.message) ||
|
172
|
-
(/^Couldn't find series: #{class_key}\/#{id}$/ === ide.message)
|
173
|
-
|
174
|
-
prior = nil
|
175
|
-
end
|
176
|
-
record = prior.nil? ? {} : prior.first.delete_if {|k,v| ["time", "sequence_number"].include?(k) }
|
177
233
|
data.delete('time') if data.has_key?('time') && data['time'].nil?
|
178
|
-
Zermelo.influxdb.write_point("#{class_key}/#{id}",
|
234
|
+
Zermelo.influxdb.write_point("#{class_key}/#{id}", data.merge('id' => id))
|
179
235
|
end
|
180
236
|
end
|
181
237
|
|
@@ -1,26 +1,30 @@
|
|
1
|
-
require 'zermelo/
|
1
|
+
require 'zermelo/backend'
|
2
2
|
|
3
|
-
require 'zermelo/filters/
|
3
|
+
require 'zermelo/filters/redis'
|
4
4
|
require 'zermelo/locks/redis_lock'
|
5
5
|
|
6
6
|
module Zermelo
|
7
7
|
|
8
8
|
module Backends
|
9
9
|
|
10
|
-
class
|
10
|
+
class Redis
|
11
11
|
|
12
|
-
include Zermelo::
|
12
|
+
include Zermelo::Backend
|
13
13
|
|
14
|
-
def default_sorted_set_key
|
15
|
-
|
16
|
-
end
|
14
|
+
# def default_sorted_set_key
|
15
|
+
# :timestamp
|
16
|
+
# end
|
17
17
|
|
18
18
|
def generate_lock
|
19
19
|
Zermelo::Locks::RedisLock.new
|
20
20
|
end
|
21
21
|
|
22
|
-
def filter(ids_key,
|
23
|
-
|
22
|
+
def filter(ids_key, associated_class, callback_target_class = nil,
|
23
|
+
callback_target_id = nil, callbacks = nil, sort_order = nil)
|
24
|
+
|
25
|
+
Zermelo::Filters::Redis.new(self, ids_key, associated_class,
|
26
|
+
callback_target_class, callback_target_id,
|
27
|
+
callbacks, sort_order)
|
24
28
|
end
|
25
29
|
|
26
30
|
def get_multiple(*attr_keys)
|
@@ -38,26 +42,25 @@ module Zermelo
|
|
38
42
|
if attr_key.accessor.nil?
|
39
43
|
Zermelo.redis.lrange(redis_attr_key, 0, -1)
|
40
44
|
else
|
41
|
-
|
45
|
+
# TODO
|
42
46
|
end
|
43
47
|
when :set
|
44
48
|
if attr_key.accessor.nil?
|
45
49
|
Set.new( Zermelo.redis.smembers(redis_attr_key) )
|
46
50
|
else
|
47
|
-
|
51
|
+
# TODO
|
48
52
|
end
|
49
53
|
when :hash
|
50
54
|
if attr_key.accessor.nil?
|
51
55
|
Zermelo.redis.hgetall(redis_attr_key)
|
52
56
|
else
|
53
|
-
|
57
|
+
# TODO
|
54
58
|
end
|
55
59
|
when :sorted_set
|
56
|
-
# TODO should this be something that preserves order?
|
57
60
|
if attr_key.accessor.nil?
|
58
|
-
|
61
|
+
Zermelo.redis.zrange(redis_attr_key, 0, -1)
|
59
62
|
else
|
60
|
-
|
63
|
+
# TODO
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -94,19 +97,6 @@ module Zermelo
|
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
97
|
-
def exists?(key)
|
98
|
-
Zermelo.redis.exists(key_to_redis_key(key))
|
99
|
-
end
|
100
|
-
|
101
|
-
def include?(key, id)
|
102
|
-
case key.type
|
103
|
-
when :set
|
104
|
-
Zermelo.redis.sismember(key_to_redis_key(key), id)
|
105
|
-
else
|
106
|
-
raise "Not implemented"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
100
|
def begin_transaction
|
111
101
|
return false unless @transaction_redis.nil?
|
112
102
|
@transaction_redis = Zermelo.redis
|
@@ -169,22 +159,11 @@ module Zermelo
|
|
169
159
|
# TODO converge usage of idx_class and _index lookup invocation
|
170
160
|
|
171
161
|
def index_lookup(att, associated_class, idx_class, value, attr_type, temp_keys)
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
# TODO (maybe) if a filter from different backend, resolve to ids and
|
176
|
-
# put that in a Redis temp set
|
177
|
-
|
178
|
-
# collection, should_be_deleted = value.resolve_steps
|
179
|
-
|
180
|
-
# if should_be_deleted
|
181
|
-
# temp_sets << collection.name
|
182
|
-
# end
|
183
|
-
|
184
|
-
# unless :set.eql?(collection.type)
|
185
|
-
# raise "Unsure as yet if non-sets are safe as Filter step values"
|
186
|
-
# end
|
162
|
+
if (idx_class == Zermelo::Associations::RangeIndex) && !value.is_a?(Zermelo::Filters::IndexRange)
|
163
|
+
raise "Range index must be passed a range"
|
164
|
+
end
|
187
165
|
|
166
|
+
case value
|
188
167
|
when Regexp
|
189
168
|
raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
|
190
169
|
|
@@ -228,13 +207,31 @@ module Zermelo
|
|
228
207
|
index = associated_class.send("#{att}_index")
|
229
208
|
|
230
209
|
case index
|
210
|
+
when Zermelo::Associations::RangeIndex
|
211
|
+
r_index_key = key_to_redis_key(index.key)
|
212
|
+
range = if value.by_score
|
213
|
+
range_start = value.start.nil? ? '-inf' : safe_value(attr_type, value.start)
|
214
|
+
range_finish = value.finish.nil? ? '+inf' : safe_value(attr_type, value.finish)
|
215
|
+
Zermelo.redis.zrangebyscore(r_index_key, range_start, range_finish)
|
216
|
+
else
|
217
|
+
range_start = value.start || 0
|
218
|
+
range_finish = value.finish || -1
|
219
|
+
Zermelo.redis.zrange(r_index_key, range_start, range_finish)
|
220
|
+
end
|
221
|
+
|
222
|
+
# TODO another way for index_lookup to indicate 'empty result', rather
|
223
|
+
# than creating & returning an empty key
|
224
|
+
idx_key = associated_class.send(:temp_key, :set)
|
225
|
+
temp_keys << idx_key
|
226
|
+
Zermelo.redis.sadd(key_to_redis_key(idx_key), range) unless range.empty?
|
227
|
+
idx_key
|
231
228
|
when Zermelo::Associations::UniqueIndex
|
232
229
|
idx_key = associated_class.send(:temp_key, :set)
|
233
230
|
temp_keys << idx_key
|
234
231
|
|
235
232
|
Zermelo.redis.sadd(key_to_redis_key(idx_key),
|
236
233
|
Zermelo.redis.hget(key_to_redis_key(index.key),
|
237
|
-
|
234
|
+
index_keys(attr_type, value).join(':')))
|
238
235
|
idx_key
|
239
236
|
when Zermelo::Associations::Index
|
240
237
|
index.key(value)
|
@@ -244,8 +241,8 @@ module Zermelo
|
|
244
241
|
|
245
242
|
private
|
246
243
|
|
247
|
-
def change(op, key, value = nil, key_to = nil)
|
248
|
-
ch = [op, key, value, key_to]
|
244
|
+
def change(op, key, value = nil, key_to = nil, value_to = nil)
|
245
|
+
ch = [op, key, value, key_to, value_to]
|
249
246
|
if @in_transaction
|
250
247
|
@changes << ch
|
251
248
|
else
|
@@ -259,10 +256,11 @@ module Zermelo
|
|
259
256
|
purges = []
|
260
257
|
|
261
258
|
changes.each do |ch|
|
262
|
-
op
|
263
|
-
key
|
264
|
-
value
|
265
|
-
key_to
|
259
|
+
op = ch[0]
|
260
|
+
key = ch[1]
|
261
|
+
value = ch[2]
|
262
|
+
key_to = ch[3]
|
263
|
+
value_to = ch[4]
|
266
264
|
|
267
265
|
# TODO check that collection types handle nil value for whole thing
|
268
266
|
if Zermelo::COLLECTION_TYPES.has_key?(key.type)
|
@@ -302,16 +300,14 @@ module Zermelo
|
|
302
300
|
when :set
|
303
301
|
Zermelo.redis.smove(complex_attr_key, key_to_redis_key(key_to), value)
|
304
302
|
when :list
|
305
|
-
# TODO
|
306
|
-
# https://github.com/antirez/redis/issues/2079
|
307
|
-
# copy the workaround from redis_filter.rb
|
303
|
+
# TODO via sort 'nosort', or the workaround required prior to
|
304
|
+
# https://github.com/antirez/redis/issues/2079
|
308
305
|
raise "Not yet implemented"
|
309
306
|
when :hash
|
310
|
-
|
311
|
-
Zermelo.redis.
|
312
|
-
Zermelo.redis.hset(key_to_redis_key(key_to), *values)
|
307
|
+
Zermelo.redis.hdel(complex_attr_key, *value.keys)
|
308
|
+
Zermelo.redis.hset(key_to_redis_key(key_to), *value_to.to_a.flatten)
|
313
309
|
when :sorted_set
|
314
|
-
|
310
|
+
Zermelo.redis.zadd(complex_attr_key, *value_to)
|
315
311
|
end
|
316
312
|
when :delete
|
317
313
|
case key.type
|
@@ -329,7 +325,6 @@ module Zermelo
|
|
329
325
|
end
|
330
326
|
|
331
327
|
elsif :purge.eql?(op)
|
332
|
-
# TODO get keys for all assocs & indices, purge them too
|
333
328
|
purges << ["#{key.klass.send(:class_key)}:#{key.id}:attrs"]
|
334
329
|
else
|
335
330
|
simple_attr_key = key_to_redis_key(key)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'zermelo/backend'
|
2
|
+
|
3
|
+
module Zermelo
|
4
|
+
|
5
|
+
module Backends
|
6
|
+
|
7
|
+
class Stub
|
8
|
+
|
9
|
+
include Zermelo::Backend
|
10
|
+
|
11
|
+
def filter(ids_key, associated_class, callback_target_class = nil,
|
12
|
+
callback_target_id = nil, callbacks = nil, sort_order = nil)
|
13
|
+
|
14
|
+
raise "Not supported"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_multiple(*attr_keys)
|
18
|
+
raise "Not supported"
|
19
|
+
end
|
20
|
+
|
21
|
+
def begin_transaction
|
22
|
+
raise "Not supported"
|
23
|
+
end
|
24
|
+
|
25
|
+
def commit_transaction
|
26
|
+
raise "Not supported"
|
27
|
+
end
|
28
|
+
|
29
|
+
def abort_transaction
|
30
|
+
raise "Not supported"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def change(op, key, value = nil, key_to = nil, value_to = nil)
|
36
|
+
# no-op
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
require 'zermelo/records/errors'
|
4
|
+
|
5
|
+
require 'zermelo/filters/steps/list_step'
|
6
|
+
require 'zermelo/filters/steps/set_step'
|
7
|
+
require 'zermelo/filters/steps/sort_step'
|
8
|
+
|
9
|
+
module Zermelo
|
10
|
+
|
11
|
+
module Filter
|
12
|
+
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
attr_reader :backend, :steps
|
16
|
+
|
17
|
+
# initial set a Zermelo::Record::Key object
|
18
|
+
# associated_class the class of the result record
|
19
|
+
# TODO hash for these params as it's getting too long
|
20
|
+
def initialize(data_backend, initial_key, associated_class,
|
21
|
+
callback_target_class = nil, callback_target_id = nil,
|
22
|
+
callbacks = nil, sort_order = nil,
|
23
|
+
ancestor = nil, step = nil)
|
24
|
+
@backend = data_backend
|
25
|
+
@initial_key = initial_key
|
26
|
+
@associated_class = associated_class
|
27
|
+
@callback_target_class = callback_target_class
|
28
|
+
@callback_target_id = callback_target_id
|
29
|
+
@callbacks = callbacks
|
30
|
+
@sort_order = sort_order
|
31
|
+
@steps = ancestor.nil? ? [] : ancestor.steps.dup
|
32
|
+
@steps << step unless step.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def intersect(attrs = {})
|
36
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
37
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
38
|
+
self, ::Zermelo::Filters::Steps::SetStep.new({:op => :intersect}, attrs))
|
39
|
+
end
|
40
|
+
|
41
|
+
def union(attrs = {})
|
42
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
43
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
44
|
+
self, ::Zermelo::Filters::Steps::SetStep.new({:op => :union}, attrs))
|
45
|
+
end
|
46
|
+
|
47
|
+
def diff(attrs = {})
|
48
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
49
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
50
|
+
self, ::Zermelo::Filters::Steps::SetStep.new({:op => :diff}, attrs))
|
51
|
+
end
|
52
|
+
|
53
|
+
def sort(keys, opts = {})
|
54
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
55
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
56
|
+
self, ::Zermelo::Filters::Steps::SortStep.new({:keys => keys,
|
57
|
+
:desc => opts[:desc], :limit => opts[:limit],
|
58
|
+
:offset => opts[:offset]}, {})
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def offset(amount, opts = {})
|
63
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
64
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
65
|
+
self, ::Zermelo::Filters::Steps::ListStep.new({:offset => amount,
|
66
|
+
:limit => opts[:limit]}, {}))
|
67
|
+
end
|
68
|
+
|
69
|
+
# (a different syntax to the above)
|
70
|
+
def page(num, opts = {})
|
71
|
+
per_page = opts[:per_page].to_i || 20
|
72
|
+
start = per_page * (num - 1)
|
73
|
+
self.class.new(@backend, @initial_key, @associated_class,
|
74
|
+
@callback_target_class, @callback_target_id, @callbacks, @sort_order,
|
75
|
+
self, ::Zermelo::Filters::Steps::ListStep.new({:offset => start,
|
76
|
+
:limit => per_page}, {}))
|
77
|
+
end
|
78
|
+
|
79
|
+
# step users
|
80
|
+
def exists?(e_id)
|
81
|
+
lock(false) { _exists?(e_id) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_by_id(f_id)
|
85
|
+
lock { _find_by_id(f_id) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_by_id!(f_id)
|
89
|
+
ret = find_by_id(f_id)
|
90
|
+
raise ::Zermelo::Records::Errors::RecordNotFound.new(@associated_class, f_id) if ret.nil?
|
91
|
+
ret
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_by_ids(*f_ids)
|
95
|
+
lock { f_ids.collect {|f_id| _find_by_id(f_id) } }
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_by_ids!(*f_ids)
|
99
|
+
ret = find_by_ids(*f_ids)
|
100
|
+
if ret.any? {|r| r.nil? }
|
101
|
+
raise ::Zermelo::Records::Errors::RecordsNotFound.new(@associated_class, f_ids - ret.compact.map(&:id))
|
102
|
+
end
|
103
|
+
ret
|
104
|
+
end
|
105
|
+
|
106
|
+
def ids
|
107
|
+
lock(false) { _ids }
|
108
|
+
end
|
109
|
+
|
110
|
+
def count
|
111
|
+
lock(false) { _count }
|
112
|
+
end
|
113
|
+
|
114
|
+
def empty?
|
115
|
+
lock(false) { _count == 0 }
|
116
|
+
end
|
117
|
+
|
118
|
+
def all
|
119
|
+
lock { _all }
|
120
|
+
end
|
121
|
+
|
122
|
+
def collect(&block)
|
123
|
+
lock { _ids.collect {|id| block.call(_load(id))} }
|
124
|
+
end
|
125
|
+
alias_method :map, :collect
|
126
|
+
|
127
|
+
def each(&block)
|
128
|
+
lock { _ids.each {|id| block.call(_load(id)) } }
|
129
|
+
end
|
130
|
+
|
131
|
+
def select(&block)
|
132
|
+
lock { _all.select {|obj| block.call(obj) } }
|
133
|
+
end
|
134
|
+
alias_method :find_all, :select
|
135
|
+
|
136
|
+
def reject(&block)
|
137
|
+
lock { _all.reject {|obj| block.call(obj)} }
|
138
|
+
end
|
139
|
+
|
140
|
+
def destroy_all
|
141
|
+
lock(*@associated_class.send(:associated_classes)) do
|
142
|
+
_all.each {|r| r.destroy }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def associated_ids_for(name, options = {})
|
147
|
+
data_type, type_klass = @associated_class.send(:with_association_data, name.to_sym) do |data|
|
148
|
+
[data.data_type, data.type_klass]
|
149
|
+
end
|
150
|
+
|
151
|
+
lock {
|
152
|
+
case data_type
|
153
|
+
when :belongs_to, :has_one
|
154
|
+
type_klass.send(:associated_ids_for, @backend, data_type,
|
155
|
+
@associated_class, name,
|
156
|
+
options[:inversed].is_a?(TrueClass), *_ids)
|
157
|
+
else
|
158
|
+
type_klass.send(:associated_ids_for, @backend, data_type,
|
159
|
+
@associated_class, name, *_ids)
|
160
|
+
end
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
protected
|
165
|
+
|
166
|
+
def lock(when_steps_empty = true, *klasses, &block)
|
167
|
+
return(block.call) if !when_steps_empty && @steps.empty?
|
168
|
+
klasses += [@associated_class] if !klasses.include?(@associated_class)
|
169
|
+
@backend.lock(*klasses, &block)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def _find_by_id(id)
|
175
|
+
if !id.nil? && _exists?(id)
|
176
|
+
_load(id.to_s)
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def _load(id)
|
183
|
+
object = @associated_class.new
|
184
|
+
object.load(id)
|
185
|
+
object
|
186
|
+
end
|
187
|
+
|
188
|
+
def _all
|
189
|
+
_ids.map {|id| _load(id) }
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Zermelo
|
2
|
+
module Filters
|
3
|
+
class IndexRange
|
4
|
+
|
5
|
+
attr_reader :start, :finish, :by_score
|
6
|
+
|
7
|
+
def initialize(start, finish, opts = {})
|
8
|
+
value_types = opts[:by_score] ? [Float, Date, Time, DateTime] : [Integer]
|
9
|
+
[start, finish].each do |v|
|
10
|
+
raise "Values must be #{value_types.join('/')}" unless v.nil? || value_types.any? {|vt| v.is_a?(vt)}
|
11
|
+
end
|
12
|
+
if !start.nil? && !finish.nil? && (start > finish)
|
13
|
+
raise "Start of range must be <= finish"
|
14
|
+
end
|
15
|
+
@start = start
|
16
|
+
@finish = finish
|
17
|
+
@by_score = opts[:by_score]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|