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,120 +0,0 @@
1
- require 'forwardable'
2
-
3
- module Zermelo
4
- module Associations
5
- class HasMany
6
-
7
- extend Forwardable
8
-
9
- def_delegators :filter, :intersect, :union, :diff, :sort,
10
- :find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
11
- :page, :all, :each, :collect, :map,
12
- :select, :find_all, :reject, :destroy_all,
13
- :ids, :count, :empty?, :exists?,
14
- :associated_ids_for
15
-
16
- def initialize(parent, name)
17
- @parent = parent
18
-
19
- @backend = parent.send(:backend)
20
-
21
- @record_ids_key = Zermelo::Records::Key.new(
22
- :klass => parent.class,
23
- :id => parent.id,
24
- :name => "#{name}_ids",
25
- :type => :set,
26
- :object => :association
27
- )
28
-
29
- parent.class.send(:with_association_data, name.to_sym) do |data|
30
- @associated_class = data.data_klass
31
- @lock_klasses = [data.data_klass] + data.related_klasses
32
- @inverse = data.inverse
33
- @callbacks = data.callbacks
34
- end
35
- end
36
-
37
- def <<(record)
38
- add(record)
39
- self # for << 'a' << 'b'
40
- end
41
-
42
- def add(*records)
43
- raise 'No records to add' if records.empty?
44
- raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
45
- raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
46
- @parent.class.lock(*@lock_klasses) do
47
- ba = @callbacks[:before_add]
48
- if ba.nil? || !@parent.respond_to?(ba) || !@parent.send(ba, *records).is_a?(FalseClass)
49
- unless @inverse.nil?
50
- records.each do |record|
51
- @associated_class.send(:load, record.id).send("#{@inverse}=", @parent)
52
- end
53
- end
54
-
55
- new_txn = @backend.begin_transaction
56
- @backend.add(@record_ids_key, records.map(&:id))
57
- @backend.commit_transaction if new_txn
58
- aa = @callbacks[:after_add]
59
- @parent.send(aa, *records) if !aa.nil? && @parent.respond_to?(aa)
60
- end
61
- end
62
- end
63
-
64
- # TODO support dependent delete, for now just deletes the association
65
- def delete(*records)
66
- raise 'No records to delete' if records.empty?
67
- raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
68
- raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?} # may need to be moved
69
- @parent.class.lock(*@lock_klasses) do
70
- br = @callbacks[:before_remove]
71
- if br.nil? || !@parent.respond_to?(br) || !@parent.send(br, *records).is_a?(FalseClass)
72
- unless @inverse.nil?
73
- records.each do |record|
74
- @associated_class.send(:load, record.id).send("#{@inverse}=", nil)
75
- end
76
- end
77
-
78
- new_txn = @backend.begin_transaction
79
- @backend.delete(@record_ids_key, records.map(&:id))
80
- @backend.commit_transaction if new_txn
81
- ar = @callbacks[:after_remove]
82
- @parent.send(ar, *records) if !ar.nil? && @parent.respond_to?(ar)
83
- end
84
- end
85
- end
86
-
87
- private
88
-
89
- # associated will be a belongs_to; on remove already runs inside a lock and transaction
90
- def on_remove
91
- unless @inverse.nil?
92
- self.ids.each do |record_id|
93
- @associated_class.send(:load, record_id).send("#{@inverse}=", nil)
94
- end
95
- end
96
- @backend.clear(@record_ids_key)
97
- end
98
-
99
- # creates a new filter class each time it's called, to store the
100
- # state for this particular filter chain
101
- def filter
102
- @backend.filter(@record_ids_key, @associated_class)
103
- end
104
-
105
- def self.associated_ids_for(backend, klass, name, *these_ids)
106
- these_ids.each_with_object({}) do |this_id, memo|
107
- key = Zermelo::Records::Key.new(
108
- :klass => klass,
109
- :id => this_id,
110
- :name => "#{name}_ids",
111
- :type => :set,
112
- :object => :association
113
- )
114
- memo[this_id] = backend.get(key)
115
- end
116
- end
117
-
118
- end
119
- end
120
- end
@@ -1,109 +0,0 @@
1
- module Zermelo
2
- module Associations
3
- class HasOne
4
-
5
- def initialize(parent, name)
6
- @parent = parent
7
-
8
- @backend = parent.send(:backend)
9
-
10
- # TODO would be better as a 'has_one' hash, a bit like belongs_to
11
- @record_id_key = Zermelo::Records::Key.new(
12
- :klass => parent.class,
13
- :id => parent.id,
14
- :name => "#{name}_id",
15
- :type => :string,
16
- :object => :association
17
- )
18
-
19
- parent.class.send(:with_association_data, name.to_sym) do |data|
20
- @associated_class = data.data_klass
21
- @lock_klasses = [data.data_klass] + data.related_klasses
22
- @inverse = data.inverse
23
- @callbacks = data.callbacks
24
- end
25
- end
26
-
27
- def value
28
- @parent.class.lock(@associated_class) do
29
- if id = @backend.get(@record_id_key)
30
- @associated_class.send(:load, id)
31
- else
32
- nil
33
- end
34
- end
35
- end
36
-
37
- def value=(record)
38
- if record.nil?
39
- @parent.class.lock(*@lock_klasses) do
40
- id = @backend.get(@record_id_key)
41
- unless id.nil?
42
- r = @associated_class.send(:load, id)
43
- unless r.nil?
44
- bc = @callbacks[:before_clear]
45
- if bc.nil? || !@parent.respond_to?(bc) || !@parent.send(bc, r).is_a?(FalseClass)
46
- delete_without_lock(r)
47
- ac = @callbacks[:after_clear]
48
- @parent.send(ac, r) if !ac.nil? && @parent.respond_to?(ac)
49
- end
50
- end
51
- end
52
- end
53
- else
54
- raise 'Invalid record class' unless record.is_a?(@associated_class)
55
- raise 'Record must have been saved' unless record.persisted?
56
- @parent.class.lock(*@lock_klasses) do
57
- bs = @callbacks[:before_set]
58
- if bs.nil? || !@parent.respond_to?(bs) || !@parent.send(bs, r).is_a?(FalseClass)
59
- unless @inverse.nil?
60
- @associated_class.send(:load, record.id).send("#{@inverse}=", @parent)
61
- end
62
-
63
- new_txn = @backend.begin_transaction
64
- @backend.set(@record_id_key, record.id)
65
- @backend.commit_transaction if new_txn
66
- as = @callbacks[:after_set]
67
- @parent.send(as, record) if !as.nil? && @parent.respond_to?(as)
68
- end
69
- end
70
- end
71
- end
72
-
73
- private
74
-
75
- def delete_without_lock(record)
76
- unless @inverse.nil?
77
- record.send("#{@inverse}=", nil)
78
- end
79
- new_txn = @backend.begin_transaction
80
- @backend.clear(@record_id_key)
81
- @backend.commit_transaction if new_txn
82
- end
83
-
84
- # associated will be a belongs_to; on_remove already runs inside lock and transaction
85
- def on_remove
86
- unless @inverse.nil?
87
- if record_id = @backend.get(@record_id_key)
88
- @associated_class.send(:load, record_id).send("#{@inverse}=", nil)
89
- end
90
- end
91
- @backend.clear(@record_id_key)
92
- end
93
-
94
- def self.associated_ids_for(backend, klass, name, *these_ids)
95
- these_ids.each_with_object({}) do |this_id, memo|
96
- key = Zermelo::Records::Key.new(
97
- :klass => klass,
98
- :id => this_id,
99
- :name => "#{name}_id",
100
- :type => :string,
101
- :object => :association
102
- )
103
- memo[this_id] = backend.get(key)
104
- end
105
- end
106
-
107
- end
108
- end
109
- end
@@ -1,124 +0,0 @@
1
- require 'forwardable'
2
-
3
- module Zermelo
4
- module Associations
5
- class HasSortedSet
6
-
7
- extend Forwardable
8
-
9
- def_delegators :filter, :intersect, :union, :diff, :sort,
10
- :intersect_range, :union_range, :diff_range,
11
- :find_by_id, :find_by_ids, :find_by_id!, :find_by_ids!,
12
- :all, :each, :collect, :map,
13
- :select, :find_all, :reject, :destroy_all,
14
- :ids, :count, :empty?, :exists?,
15
- :first, :last,
16
- :associated_ids_for
17
-
18
- def initialize(parent, name)
19
- @parent = parent
20
-
21
- @backend = parent.send(:backend)
22
-
23
- @record_ids_key = Zermelo::Records::Key.new(
24
- :klass => parent.class,
25
- :id => parent.id,
26
- :name => "#{name}_ids",
27
- :type => :sorted_set,
28
- :object => :association
29
- )
30
-
31
- parent.class.send(:with_association_data, name.to_sym) do |data|
32
- @associated_class = data.data_klass
33
- @lock_klasses = [data.data_klass] + data.related_klasses
34
- @inverse = data.inverse
35
- @sort_key = data.sort_key || backend.default_sort_key
36
- @callbacks = data.callbacks
37
- end
38
- end
39
-
40
- def <<(record)
41
- add(record)
42
- self # for << 'a' << 'b'
43
- end
44
-
45
- # TODO collect all scores/ids and do a single zadd/single hmset
46
- def add(*records)
47
- raise 'No records to add' if records.empty?
48
- raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
49
- raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?}
50
- @parent.class.lock(*@lock_klasses) do
51
- ba = @callbacks[:before_add]
52
- if ba.nil? || !@parent.respond_to?(ba) || !@parent.send(ba, *records).is_a?(FalseClass)
53
- unless @inverse.nil?
54
- records.each do |record|
55
- @associated_class.send(:load, record.id).send("#{@inverse}=", @parent)
56
- end
57
- end
58
-
59
- new_txn = @backend.begin_transaction
60
- @backend.add(@record_ids_key, (records.map {|r| [r.send(@sort_key.to_sym).to_f, r.id]}.flatten))
61
- @backend.commit_transaction if new_txn
62
- aa = @callbacks[:after_add]
63
- @parent.send(aa, *records) if !aa.nil? && @parent.respond_to?(aa)
64
- end
65
- end
66
- end
67
-
68
- def delete(*records)
69
- raise 'No records to delete' if records.empty?
70
- raise 'Invalid record class' if records.any? {|r| !r.is_a?(@associated_class)}
71
- raise 'Record(s) must have been saved' unless records.all? {|r| r.persisted?}
72
- @parent.class.lock(*@lock_klasses) do
73
- br = @callbacks[:before_remove]
74
- if br.nil? || !@parent.respond_to?(br) || !@parent.send(br, *records).is_a?(FalseClass)
75
- unless @inverse.nil?
76
- records.each do |record|
77
- @associated_class.send(:load, record.id).send("#{@inverse}=", nil)
78
- end
79
- end
80
-
81
- new_txn = @backend.begin_transaction
82
- @backend.delete(@record_ids_key, records.map(&:id))
83
- @backend.commit_transaction if new_txn
84
- ar = @callbacks[:after_remove]
85
- @parent.send(ar, *records) if !ar.nil? && @parent.respond_to?(ar)
86
- end
87
- end
88
- end
89
-
90
- private
91
-
92
- # associated will be a belongs_to; on remove already runs inside a lock and transaction
93
- def on_remove
94
- unless @inverse.nil?
95
- self.ids.each do |record_id|
96
- # clear the belongs_to inverse value with this @parent.id
97
- @associated_class.send(:load, record_id).send("#{@inverse}=", nil)
98
- end
99
- end
100
- @backend.clear(@record_ids_key)
101
- end
102
-
103
- # creates a new filter class each time it's called, to store the
104
- # state for this particular filter chain
105
- def filter
106
- @backend.filter(@record_ids_key, @associated_class)
107
- end
108
-
109
- def self.associated_ids_for(backend, klass, name, *these_ids)
110
- these_ids.each_with_object({}) do |this_id, memo|
111
- key = Zermelo::Records::Key.new(
112
- :klass => klass,
113
- :id => this_id,
114
- :name => "#{name}_ids",
115
- :type => :sorted_set,
116
- :object => :association
117
- )
118
- memo[this_id] = backend.get(key)
119
- end
120
- end
121
-
122
- end
123
- end
124
- end
@@ -1,115 +0,0 @@
1
- require 'active_support/concern'
2
-
3
- require 'zermelo/locks/no_lock'
4
-
5
- module Zermelo
6
-
7
- module Backends
8
-
9
- module Base
10
-
11
- extend ActiveSupport::Concern
12
-
13
- def escape_key_name(name)
14
- name.gsub(/%/, '%%').gsub(/ /, '%20').gsub(/:/, '%3A')
15
- end
16
-
17
- def unescape_key_name(name)
18
- name.gsub(/%3A/, ':').gsub(/%20/, ' ').gsub(/%%/, '%')
19
- end
20
-
21
- def index_keys(type, value)
22
- return ["null", "null"] if value.nil?
23
-
24
- case type
25
- when :string
26
- ["string", escape_key_name(value)]
27
- when :integer
28
- ["integer", escape_key_name(value.to_s)]
29
- when :float
30
- ["float", escape_key_name(value.to_s)]
31
- when :timestamp
32
- case value
33
- when Integer
34
- ["timestamp", escape_key_name(value.to_s)]
35
- when Time, DateTime
36
- ["timestamp", escape_key_name(value.to_i.to_s)]
37
- end
38
- when :boolean
39
- case value
40
- when TrueClass
41
- ["boolean", "true"]
42
- when FalseClass
43
- ["boolean", "false"]
44
- end
45
- end
46
- end
47
-
48
- # for hashes, lists, sets
49
- def add(key, value)
50
- change(:add, key, value)
51
- end
52
-
53
- def delete(key, value)
54
- change(:delete, key, value)
55
- end
56
-
57
- def move(key, value, key_to)
58
- change(:move, key, value, key_to)
59
- end
60
-
61
- def clear(key)
62
- change(:clear, key)
63
- end
64
-
65
- # works for both simple and complex types (i.e. strings, numbers, booleans,
66
- # hashes, lists, sets)
67
- def set(key, value)
68
- change(:set, key, value)
69
- end
70
-
71
- def purge(key)
72
- change(:purge, key)
73
- end
74
-
75
- def get(attr_key)
76
- get_multiple(attr_key)[attr_key.klass.send(:class_key)][attr_key.id][attr_key.name.to_s]
77
- end
78
-
79
- def lock(*klasses, &block)
80
- ret = nil
81
- # doesn't handle re-entrant case for influxdb, which has no locking yet
82
- locking = Thread.current[:zermelo_locking]
83
- if locking.nil?
84
- lock_proc = proc do
85
- begin
86
- Thread.current[:zermelo_locking] = klasses
87
- ret = block.call
88
- ensure
89
- Thread.current[:zermelo_locking] = nil
90
- end
91
- end
92
-
93
- lock_klass = case self
94
- when Zermelo::Backends::RedisBackend
95
- Zermelo::Locks::RedisLock
96
- else
97
- Zermelo::Locks::NoLock
98
- end
99
-
100
- lock_klass.new.lock(*klasses, &lock_proc)
101
- else
102
- # accepts any subset of 'locking'
103
- unless (klasses - locking).empty?
104
- raise "Currently locking #{locking.map(&:name)}, cannot lock different set #{klasses.map(&:name)}"
105
- end
106
- ret = block.call
107
- end
108
- ret
109
- end
110
-
111
- end
112
-
113
- end
114
-
115
- end
@@ -1,212 +0,0 @@
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/sorted_set_step'
8
- require 'zermelo/filters/steps/sort_step'
9
-
10
- module Zermelo
11
-
12
- module Filters
13
-
14
- module Base
15
-
16
- extend ActiveSupport::Concern
17
-
18
- attr_reader :backend, :steps
19
-
20
- # initial set a Zermelo::Record::Key object
21
- # associated_class the class of the result record
22
- def initialize(data_backend, initial_key, associated_class, ancestor = nil, step = nil)
23
- @backend = data_backend
24
- @initial_key = initial_key
25
- @associated_class = associated_class
26
- @steps = ancestor.nil? ? [] : ancestor.steps.dup
27
- @steps << step unless step.nil?
28
- end
29
-
30
- def intersect(attrs = {})
31
- self.class.new(@backend, @initial_key, @associated_class, self,
32
- ::Zermelo::Filters::Steps::SetStep.new({:op => :intersect}, attrs))
33
- end
34
-
35
- def union(attrs = {})
36
- self.class.new(@backend, @initial_key, @associated_class, self,
37
- ::Zermelo::Filters::Steps::SetStep.new({:op => :union}, attrs))
38
- end
39
-
40
- def diff(attrs = {})
41
- self.class.new(@backend, @initial_key, @associated_class, self,
42
- ::Zermelo::Filters::Steps::SetStep.new({:op => :diff}, attrs))
43
- end
44
-
45
- def sort(keys, opts = {})
46
- self.class.new(@backend, @initial_key, @associated_class, self,
47
- ::Zermelo::Filters::Steps::SortStep.new({:keys => keys,
48
- :desc => opts[:desc], :limit => opts[:limit],
49
- :offset => opts[:offset]}, {})
50
- )
51
- end
52
-
53
- def offset(amount, opts = {})
54
- self.class.new(@backend, @initial_key, @associated_class, self,
55
- ::Zermelo::Filters::Steps::ListStep.new({:offset => amount,
56
- :limit => opts[:limit]}, {}))
57
- end
58
-
59
- def page(num, opts = {})
60
- per_page = opts[:per_page].to_i || 20
61
- start = per_page * (num - 1)
62
- finish = start + (per_page - 1)
63
- self.class.new(@backend, @initial_key, @associated_class, self,
64
- ::Zermelo::Filters::Steps::ListStep.new({:offset => start,
65
- :limit => per_page}, {}))
66
- end
67
-
68
- def intersect_range(start, finish, attrs_opts = {})
69
- self.class.new(@backend, @initial_key, @associated_class, self,
70
- ::Zermelo::Filters::Steps::SortedSetStep.new(
71
- {:op => :intersect_range, :start => start, :finish => finish,
72
- :by_score => attrs_opts.delete(:by_score)},
73
- attrs_opts)
74
- )
75
- end
76
-
77
- def union_range(start, finish, attrs_opts = {})
78
- self.class.new(@backend, @initial_key, @associated_class, self,
79
- ::Zermelo::Filters::Steps::SortedSetStep.new(
80
- {:op => :union_range, :start => start, :finish => finish,
81
- :by_score => attrs_opts.delete(:by_score)},
82
- attrs_opts)
83
- )
84
- end
85
-
86
- def diff_range(start, finish, attrs_opts = {})
87
- self.class.new(@backend, @initial_key, @associated_class, self,
88
- ::Zermelo::Filters::Steps::SortedSetStep.new(
89
- {:op => :diff_range, :start => start, :finish => finish,
90
- :by_score => attrs_opts.delete(:by_score)},
91
- attrs_opts)
92
- )
93
- end
94
-
95
- # step users
96
- def exists?(e_id)
97
- lock(false) { _exists?(e_id) }
98
- end
99
-
100
- def find_by_id(f_id)
101
- lock { _find_by_id(f_id) }
102
- end
103
-
104
- def find_by_id!(f_id)
105
- ret = lock { _find_by_id(f_id) }
106
- raise ::Zermelo::Records::Errors::RecordNotFound.new(@associated_class, f_id) if ret.nil?
107
- ret
108
- end
109
-
110
- def find_by_ids(*f_ids)
111
- lock { f_ids.collect {|f_id| _find_by_id(f_id) } }
112
- end
113
-
114
- def find_by_ids!(*f_ids)
115
- ret = lock { f_ids.collect {|f_id| _find_by_id(f_id) } }
116
- if ret.any? {|r| r.nil? }
117
- raise ::Zermelo::Records::Errors::RecordsNotFound.new(@associated_class, f_ids - ret.compact.map(&:id))
118
- end
119
- ret
120
- end
121
-
122
- def ids
123
- lock(false) { _ids }
124
- end
125
-
126
- def count
127
- lock(false) { _count }
128
- end
129
-
130
- def empty?
131
- lock(false) { _count == 0 }
132
- end
133
-
134
- def all
135
- lock { _all }
136
- end
137
-
138
- def collect(&block)
139
- lock { _ids.collect {|id| block.call(_load(id))} }
140
- end
141
- alias_method :map, :collect
142
-
143
- def each(&block)
144
- lock { _ids.each {|id| block.call(_load(id)) } }
145
- end
146
-
147
- def select(&block)
148
- lock { _all.select {|obj| block.call(obj) } }
149
- end
150
- alias_method :find_all, :select
151
-
152
- def reject(&block)
153
- lock { _all.reject {|obj| block.call(obj)} }
154
- end
155
-
156
- def destroy_all
157
- lock(*@associated_class.send(:associated_classes)) do
158
- _all.each {|r| r.destroy }
159
- end
160
- end
161
-
162
- def associated_ids_for(name, options = {})
163
- klass = @associated_class.send(:with_association_data, name.to_sym) do |data|
164
- data.type_klass
165
- end
166
-
167
- lock {
168
- case klass.name
169
- when ::Zermelo::Associations::BelongsTo.name
170
- klass.send(:associated_ids_for, @backend,
171
- @associated_class, name,
172
- options[:inversed].is_a?(TrueClass), *_ids)
173
- else
174
- klass.send(:associated_ids_for, @backend,
175
- @associated_class, name, *_ids)
176
- end
177
- }
178
- end
179
-
180
- protected
181
-
182
- def lock(when_steps_empty = true, *klasses, &block)
183
- return(block.call) if !when_steps_empty && @steps.empty?
184
- klasses += [@associated_class] if !klasses.include?(@associated_class)
185
- @backend.lock(*klasses, &block)
186
- end
187
-
188
- private
189
-
190
- def _find_by_id(id)
191
- if !id.nil? && _exists?(id)
192
- _load(id.to_s)
193
- else
194
- nil
195
- end
196
- end
197
-
198
- def _load(id)
199
- object = @associated_class.new
200
- object.load(id)
201
- object
202
- end
203
-
204
- def _all
205
- _ids.map {|id| _load(id) }
206
- end
207
-
208
- end
209
-
210
- end
211
-
212
- end