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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -1,6 +1,6 @@
1
- require 'zermelo/backends/base'
1
+ require 'zermelo/backend'
2
2
 
3
- require 'zermelo/filters/influxdb_filter'
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 InfluxDBBackend
14
+ class InfluxDB
15
15
 
16
- include Zermelo::Backends::Base
16
+ include Zermelo::Backend
17
17
 
18
- def default_sorted_set_key
19
- :time
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
- def filter(ids_key, record)
23
- Zermelo::Filters::InfluxDBFilter.new(self, ids_key, record)
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
- nil
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][key.name] = case op
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 :list, :hash
152
- value
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
- (1...value.size).step(2).collect {|i| value[i] }
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}", record.merge(data).merge('id' => 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/backends/base'
1
+ require 'zermelo/backend'
2
2
 
3
- require 'zermelo/filters/redis_filter'
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 RedisBackend
10
+ class Redis
11
11
 
12
- include Zermelo::Backends::Base
12
+ include Zermelo::Backend
13
13
 
14
- def default_sorted_set_key
15
- :timestamp
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, record)
23
- Zermelo::Filters::RedisFilter.new(self, ids_key, record)
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
- Set.new( Zermelo.redis.zrange(redis_attr_key, 0, -1) )
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
- case value
173
- # when Zermelo::Filters::RedisFilter
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
- index_keys(attr_type, value).join(':')))
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 = ch[0]
263
- key = ch[1]
264
- value = ch[2]
265
- key_to = ch[3]
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 would do via sort 'nosort', except for
306
- # https://github.com/antirez/redis/issues/2079 -- instead,
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
- values = value.to_a.flatten
311
- Zermelo.redis.hdel(complex_attr_key, values)
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
- raise "Not yet implemented"
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