zermelo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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