massive_record 0.2.1 → 0.2.2.rc1

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 (135) hide show
  1. data/CHANGELOG.md +58 -2
  2. data/Gemfile.lock +17 -17
  3. data/README.md +98 -41
  4. data/lib/massive_record.rb +2 -1
  5. data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
  6. data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
  7. data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
  8. data/lib/massive_record/adapters/thrift/row.rb +35 -4
  9. data/lib/massive_record/adapters/thrift/table.rb +49 -12
  10. data/lib/massive_record/orm/attribute_methods.rb +77 -5
  11. data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
  12. data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
  13. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
  14. data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
  15. data/lib/massive_record/orm/base.rb +62 -8
  16. data/lib/massive_record/orm/column.rb +7 -11
  17. data/lib/massive_record/orm/default_id.rb +1 -1
  18. data/lib/massive_record/orm/embedded.rb +66 -0
  19. data/lib/massive_record/orm/errors.rb +17 -0
  20. data/lib/massive_record/orm/finders.rb +124 -71
  21. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
  22. data/lib/massive_record/orm/finders/scope.rb +58 -34
  23. data/lib/massive_record/orm/id_factory.rb +22 -105
  24. data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
  25. data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
  26. data/lib/massive_record/orm/identity_map.rb +256 -0
  27. data/lib/massive_record/orm/log_subscriber.rb +18 -0
  28. data/lib/massive_record/orm/observer.rb +69 -0
  29. data/lib/massive_record/orm/persistence.rb +47 -119
  30. data/lib/massive_record/orm/persistence/operations.rb +100 -0
  31. data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
  32. data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
  33. data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
  34. data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
  35. data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
  36. data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
  37. data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
  38. data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
  39. data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
  40. data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
  41. data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
  42. data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
  43. data/lib/massive_record/orm/query_instrumentation.rb +26 -49
  44. data/lib/massive_record/orm/raw_data.rb +47 -0
  45. data/lib/massive_record/orm/relations.rb +4 -0
  46. data/lib/massive_record/orm/relations/interface.rb +134 -0
  47. data/lib/massive_record/orm/relations/metadata.rb +58 -12
  48. data/lib/massive_record/orm/relations/proxy.rb +17 -12
  49. data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
  50. data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
  51. data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
  52. data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
  53. data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
  54. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  55. data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
  56. data/lib/massive_record/orm/schema/column_family.rb +3 -2
  57. data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
  58. data/lib/massive_record/orm/schema/field.rb +2 -0
  59. data/lib/massive_record/orm/schema/table_interface.rb +19 -2
  60. data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
  61. data/lib/massive_record/orm/timestamps.rb +17 -7
  62. data/lib/massive_record/orm/validations.rb +4 -0
  63. data/lib/massive_record/orm/validations/associated.rb +50 -0
  64. data/lib/massive_record/rails/railtie.rb +31 -0
  65. data/lib/massive_record/version.rb +1 -1
  66. data/lib/massive_record/wrapper/cell.rb +8 -1
  67. data/massive_record.gemspec +4 -4
  68. data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
  69. data/spec/adapter/thrift/table_find_spec.rb +14 -2
  70. data/spec/adapter/thrift/table_spec.rb +6 -6
  71. data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
  72. data/spec/orm/cases/attribute_methods_spec.rb +215 -22
  73. data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
  74. data/spec/orm/cases/change_id_spec.rb +62 -0
  75. data/spec/orm/cases/default_id_spec.rb +25 -6
  76. data/spec/orm/cases/default_values_spec.rb +6 -3
  77. data/spec/orm/cases/dirty_spec.rb +150 -102
  78. data/spec/orm/cases/embedded_spec.rb +250 -0
  79. data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
  80. data/spec/orm/cases/finder_scope_spec.rb +96 -29
  81. data/spec/orm/cases/finders_spec.rb +57 -10
  82. data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
  83. data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
  84. data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
  85. data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
  86. data/spec/orm/cases/log_subscriber_spec.rb +15 -2
  87. data/spec/orm/cases/observing_spec.rb +61 -0
  88. data/spec/orm/cases/persistence_spec.rb +151 -60
  89. data/spec/orm/cases/raw_data_spec.rb +58 -0
  90. data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
  91. data/spec/orm/cases/table_spec.rb +3 -3
  92. data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
  93. data/spec/orm/cases/timestamps_spec.rb +23 -109
  94. data/spec/orm/cases/validation_spec.rb +9 -0
  95. data/spec/orm/models/address.rb +5 -1
  96. data/spec/orm/models/address_with_timestamp.rb +12 -0
  97. data/spec/orm/models/car.rb +5 -0
  98. data/spec/orm/models/person.rb +13 -1
  99. data/spec/orm/models/person_with_timestamp.rb +4 -2
  100. data/spec/orm/models/test_class.rb +1 -0
  101. data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
  102. data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
  103. data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
  104. data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
  105. data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
  106. data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
  107. data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
  108. data/spec/orm/persistence/operations/insert_spec.rb +31 -0
  109. data/spec/orm/persistence/operations/reload_spec.rb +48 -0
  110. data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
  111. data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
  112. data/spec/orm/persistence/operations/update_spec.rb +25 -0
  113. data/spec/orm/persistence/operations_spec.rb +58 -0
  114. data/spec/orm/relations/interface_spec.rb +188 -0
  115. data/spec/orm/relations/metadata_spec.rb +92 -15
  116. data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
  117. data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
  118. data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
  119. data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
  120. data/spec/orm/schema/column_family_spec.rb +21 -0
  121. data/spec/orm/schema/embedded_interface_spec.rb +181 -0
  122. data/spec/orm/schema/field_spec.rb +7 -0
  123. data/spec/orm/schema/table_interface_spec.rb +31 -1
  124. data/spec/shared/orm/id_factories.rb +44 -0
  125. data/spec/shared/orm/model_with_timestamps.rb +132 -0
  126. data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
  127. data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
  128. data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
  129. data/spec/shared/orm/relations/proxy.rb +9 -2
  130. data/spec/spec_helper.rb +9 -0
  131. data/spec/support/mock_massive_record_connection.rb +2 -1
  132. metadata +106 -21
  133. data/spec/orm/cases/column_spec.rb +0 -49
  134. data/spec/orm/cases/id_factory_spec.rb +0 -92
  135. data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -0,0 +1,15 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Relations
4
+ class Proxy
5
+ class EmbeddedInPolymorphic < EmbeddedIn
6
+ private
7
+
8
+ # Skip check on polymorphic relation
9
+ def raise_if_type_mismatch(record)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,215 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Relations
4
+ class Proxy
5
+ class EmbedsMany < ProxyCollection
6
+ def find(id)
7
+ record = if loaded? || proxy_owner.new_record?
8
+ proxy_target.detect { |record| record.id == id }
9
+ else
10
+ find_one_embedded_record_from_raw_data(id)
11
+ end
12
+
13
+ record or raise RecordNotFound.new("Could not find #{proxy_target_class.model_name} with id=#{id}")
14
+ end
15
+
16
+ def limit(limit)
17
+ load_proxy_target.slice(0, limit)
18
+ end
19
+
20
+
21
+ #
22
+ # Adding record(s) to the collection.
23
+ #
24
+ def <<(*records)
25
+ records.flatten.each do |record|
26
+ unless include? record
27
+ raise_if_type_mismatch(record)
28
+ proxy_target << record
29
+ record.send(metadata.inverse_of).replace(proxy_owner, false)
30
+ end
31
+ end
32
+
33
+ if proxy_owner.persisted?
34
+ proxy_owner.save
35
+ else
36
+ proxy_target.sort_by! &:id
37
+ end
38
+
39
+ self
40
+ end
41
+ alias_method :push, :<<
42
+ alias_method :concat, :<<
43
+
44
+
45
+ #
46
+ # Checks if record is included in collection
47
+ #
48
+ def include?(record)
49
+ load_proxy_target.include? record
50
+ end
51
+
52
+ def length
53
+ load_proxy_target.length
54
+ end
55
+ alias_method :count, :length
56
+ alias_method :size, :length
57
+
58
+
59
+
60
+
61
+
62
+ #
63
+ # Returns the raw hash of attributes for embedded objects
64
+ # It filters away database_ids (keys in a column family)
65
+ # which it does not recognize.
66
+ #
67
+ def proxy_targets_raw # :nodoc:
68
+ Hash[proxy_owner.raw_data[metadata.store_in].collect do |database_id, value|
69
+ begin
70
+ base_class, id = Embedded.parse_database_id(database_id)
71
+ [id, value] if base_class == proxy_target_class.base_class.to_s.underscore
72
+ rescue InvalidEmbeddedDatabaseId
73
+ end
74
+ end.compact]
75
+ end
76
+
77
+
78
+
79
+
80
+ #
81
+ # Hook which are called just before save.
82
+ # It iterates over new or changed records, asking them to "save" themself.
83
+ # This will result in created_at / updated_at and persistence state being set.
84
+ # It will also build the proxy_targets_update_hash with these
85
+ # changes, which will be used at the proxy owner's save for actually updating
86
+ # these records.
87
+ #
88
+ def parent_will_be_saved! # :nodoc:
89
+ proxy_targets_update_hash.clear
90
+
91
+ MassiveRecord::ORM::Persistence::Operations.suppress do
92
+ proxy_target.each do |record|
93
+ if record.destroyed?
94
+ proxy_targets_update_hash[record.database_id] = nil
95
+ elsif record.new_record? || record.changed?
96
+ record.save
97
+ proxy_targets_update_hash[record.database_id] = Base.coder.dump(record.attributes_db_raw_data_hash)
98
+ end
99
+ end
100
+
101
+ to_be_destroyed.each do |record|
102
+ targets_current_owner = record.send(metadata.inverse_of).proxy_target
103
+ if targets_current_owner.nil? || targets_current_owner == proxy_owner
104
+ record.destroy
105
+ proxy_targets_update_hash[record.database_id] = nil
106
+ end
107
+ end
108
+ to_be_destroyed.clear
109
+ end
110
+ end
111
+
112
+ # Hook to call when save is done through parent
113
+ def parent_has_been_saved!
114
+ reload_raw_data
115
+ proxy_targets_update_hash.clear
116
+ end
117
+
118
+ def proxy_targets_update_hash
119
+ @proxy_targets_update_hash ||= {}
120
+ end
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+ def changed?
130
+ to_be_destroyed.any? || proxy_target.any? do |record|
131
+ record.new_record? || record.destroyed? || record.changed?
132
+ end
133
+ end
134
+
135
+ def changes
136
+ Hash[proxy_target.collect do |record|
137
+ if record.changed?
138
+ [record.id, record.changes]
139
+ end
140
+ end.compact]
141
+ end
142
+
143
+
144
+
145
+
146
+ private
147
+
148
+ def find_proxy_target(options = {})
149
+ reload_raw_data if proxy_targets_raw.empty?
150
+
151
+ proxy_targets_raw.inject([]) do |records, (id, raw_data)|
152
+ records << instantiate_target_class(id, raw_data)
153
+ end
154
+ end
155
+
156
+ #
157
+ # Replaces the raw_data hash in parent with reloaded data from database
158
+ #
159
+ def reload_raw_data
160
+ if proxy_owner.persisted?
161
+ reloaded_data = proxy_owner.class.select(metadata.store_in).find(proxy_owner.id).raw_data[metadata.store_in]
162
+ proxy_owner.update_raw_data_for_column_family(metadata.store_in, reloaded_data)
163
+ end
164
+ rescue MassiveRecord::ORM::RecordNotFound
165
+ # When we try to load raw data we might end up getting nil back, even though
166
+ # a row exists with given id. The reason for this is that when only selecting
167
+ # one column family (family for embedded records) and that family is empty for
168
+ # a given row we'll end up getting nil back, resulting in a record not found error.
169
+ end
170
+
171
+
172
+ def find_one_embedded_record_from_raw_data(id)
173
+ raw_data = proxy_targets_raw[id] || load_raw_data_for_id(id)
174
+ instantiate_target_class(id, raw_data) if raw_data
175
+ end
176
+
177
+ def load_raw_data_for_id(id)
178
+ database_id = Embedded.database_id(proxy_target_class, id)
179
+ if cell = proxy_owner.class.table.get_cell(proxy_owner.id, metadata.store_in, database_id)
180
+ RawData.new_with_data_from cell
181
+ end
182
+ end
183
+
184
+ # FIXME Common to all proxies representing multiple values
185
+ def find_proxy_target_with_proc(options = {}, &block)
186
+ Array(super).compact
187
+ end
188
+
189
+ def delete_or_destroy(*records, method)
190
+ records.flatten!
191
+ self.proxy_target -= records
192
+ to_be_destroyed.concat(records).uniq!
193
+ proxy_owner.save if proxy_owner.persisted? && method == :destroy
194
+ records
195
+ end
196
+
197
+ def can_find_proxy_target?
198
+ true
199
+ end
200
+
201
+
202
+ def to_be_destroyed
203
+ @to_be_destroyed ||= []
204
+ end
205
+
206
+ def instantiate_target_class(id, raw_data)
207
+ proxy_target_class.send(:instantiate, *proxy_target_class.transpose_raw_data_to_record_attributes_and_raw_data(id, raw_data)).tap do |record|
208
+ record.send(metadata.inverse_of).replace(proxy_owner, false)
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -2,41 +2,21 @@ module MassiveRecord
2
2
  module ORM
3
3
  module Relations
4
4
  class Proxy
5
- class ReferencesMany < Proxy
6
- #
7
- # Loading proxy_targets will merge it with records found currently in proxy,
8
- # to make sure we don't remove any pushed proxy_targets only cause we load the
9
- # proxy_targets.
10
- #
11
- # TODO - Implement methods like:
12
- # * find_in_batches
13
- # * find_each
14
- # * etc :-)
5
+ class ReferencesMany < ProxyCollection
6
+
15
7
  #
16
- # - A counter cache is also nice.
8
+ # Raised when we are in a references many relationship where the
9
+ # target's foreign keys are persisted in the owner and you try to
10
+ # do a person.cars.all(:limit => 1, :offset => "something") and
11
+ # some of these options are unsupported. The reason for these being
12
+ # unsupported is that we have to implement offset and limitiation
13
+ # in pure Ruby working on that car_ids array in the person. Its
14
+ # nothing close to impossible; it just has not been done yet.
17
15
  #
18
- def load_proxy_target
19
- proxy_target_before_load = proxy_target
20
- proxy_target_after_load = super
21
-
22
- self.proxy_target = (proxy_target_before_load + proxy_target_after_load).uniq
16
+ class UnsupportedFinderOption < MassiveRecordError
17
+ OPTIONS = %w(limit offset starts_with)
23
18
  end
24
19
 
25
- def reset
26
- super
27
- @proxy_target = []
28
- end
29
-
30
- def replace(*records)
31
- records.flatten!
32
-
33
- if records.length == 1 and records.first.nil?
34
- reset
35
- else
36
- delete_all
37
- concat(records)
38
- end
39
- end
40
20
 
41
21
 
42
22
  #
@@ -63,74 +43,49 @@ module MassiveRecord
63
43
  alias_method :push, :<<
64
44
  alias_method :concat, :<<
65
45
 
66
- #
67
- # Destroy record(s) from the collection
68
- # Each record will be asked to destroy itself as well
69
- #
70
- def destroy(*records)
71
- delete_or_destroy *records, :destroy
72
- end
73
-
74
- #
75
- # Deletes record(s) from the collection
76
- #
77
- def delete(*records)
78
- delete_or_destroy *records, :delete
79
- end
80
-
81
- #
82
- # Destroys all records
83
- #
84
- def destroy_all
85
- destroy(load_proxy_target)
86
- reset
87
- loaded!
88
- end
89
-
90
- #
91
- # Deletes all records from the relationship.
92
- # Does not destroy the records
93
- #
94
- def delete_all
95
- delete(load_proxy_target)
96
- reset
97
- loaded!
98
- end
99
46
 
100
47
  #
101
48
  # Checks if record is included in collection
102
49
  #
103
- # TODO This needs a bit of work, depending on if proxy's proxy_target
104
- # has been loaded or not. For now, we are just checking
105
- # what we currently have in @proxy_target
106
- #
107
- def include?(record)
108
- load_proxy_target.include? record
50
+ def include?(record_or_id)
51
+ id = record_or_id.respond_to?(:id) ? record_or_id.id : record_or_id
52
+
53
+ if loaded? || find_with_proc?
54
+ !!find(id)
55
+ else
56
+ foreign_key_in_proxy_owner_exists? id
57
+ end
58
+ rescue RecordNotFound
59
+ false
109
60
  end
110
61
 
111
62
  #
112
63
  # Returns the length of targes
113
64
  #
114
- # TODO This can be smarter as well. For instance; if we have not
115
- # loaded targets, and we have foreign keys in the owner we
116
- # can simply do a owner's foreign keys and ask for it's length.
117
- #
118
65
  def length
119
- load_proxy_target.length
66
+ if loaded?
67
+ proxy_target.length
68
+ elsif find_with_proc?
69
+ load_proxy_target.length
70
+ else
71
+ foreign_keys_in_proxy_owner.length
72
+ end
120
73
  end
121
74
  alias_method :count, :length
122
75
  alias_method :size, :length
123
76
 
124
- def empty?
125
- length == 0
77
+ def any?
78
+ if !loaded? && find_with_proc?
79
+ !!first
80
+ else
81
+ !empty?
82
+ end
126
83
  end
84
+ alias_method :present?, :any?
127
85
 
128
- def first
129
- limit(1).first
130
- end
131
86
 
132
87
  def find(id)
133
- if loaded?
88
+ if loaded? || proxy_owner.new_record?
134
89
  record = proxy_target.find { |record| record.id == id }
135
90
  elsif find_with_proc?
136
91
  if id.starts_with? proxy_owner.send(metadata.records_starts_from)
@@ -145,6 +100,53 @@ module MassiveRecord
145
100
  record
146
101
  end
147
102
 
103
+ def all(options = {})
104
+ options = MassiveRecord::Adapters::Thrift::Table.warn_and_change_deprecated_finder_options(options)
105
+
106
+ load_proxy_target(options)
107
+ end
108
+
109
+ #
110
+ # Find records in batches, yields batch into your block
111
+ #
112
+ # Options:
113
+ # <tt>:batch_size</tt> The number of records you want per batch. Defaults to 1000
114
+ # <tt>:starts_with</tt> The ids starts with this
115
+ #
116
+ def find_in_batches(options = {}, &block)
117
+ options = MassiveRecord::Adapters::Thrift::Table.warn_and_change_deprecated_finder_options(options)
118
+
119
+ options[:batch_size] ||= 1000
120
+
121
+ if loaded?
122
+ collection = if options[:starts_with]
123
+ proxy_target.select { |r| r.id.starts_with? options[:starts_with] }
124
+ else
125
+ proxy_target
126
+ end
127
+ collection.in_groups_of(options[:batch_size], false, &block)
128
+ elsif find_with_proc?
129
+ find_proxy_target_with_proc(options.merge(:finder_method => :find_in_batches), &block)
130
+ else
131
+ all_ids = proxy_owner.send(metadata.foreign_key)
132
+ all_ids = all_ids.select { |id| id.starts_with? options[:starts_with] } if options[:starts_with]
133
+ all_ids.in_groups_of(options[:batch_size]).each do |ids_in_batch|
134
+ yield Array(find_proxy_target(:ids => ids_in_batch))
135
+ end
136
+ end
137
+ end
138
+
139
+ #
140
+ # Fetches records in batches of 1000 (by default), iterates over each batch
141
+ # and yields one and one record in to given block. See find_in_batches for
142
+ # options.
143
+ #
144
+ def find_each(options = {})
145
+ find_in_batches(options) do |batch|
146
+ batch.each { |record| yield record }
147
+ end
148
+ end
149
+
148
150
  #
149
151
  # Returns a limited result set of target records.
150
152
  #
@@ -153,25 +155,31 @@ module MassiveRecord
153
155
  # than foreign keys length.
154
156
  #
155
157
  def limit(limit)
156
- if loaded?
158
+ if loaded? || proxy_owner.new_record?
157
159
  proxy_target.slice(0, limit)
158
160
  elsif find_with_proc?
159
161
  find_proxy_target_with_proc(:limit => limit)
160
162
  else
161
163
  ids = proxy_owner.send(metadata.foreign_key).slice(0, limit)
162
164
  ids = ids.first if ids.length == 1
163
- [find_proxy_target(ids)].flatten
165
+ Array(find_proxy_target(:ids => ids))
164
166
  end
165
167
  end
166
168
 
167
169
 
170
+ def is_a?(klass)
171
+ klass == Array
172
+ end
168
173
 
169
174
  private
170
175
 
171
176
 
172
177
  def delete_or_destroy(*records, method)
178
+ removed = []
179
+
173
180
  records.flatten.each do |record|
174
181
  if include? record
182
+ removed << record
175
183
  remove_foreign_key_in_proxy_owner(record.id)
176
184
  proxy_target.delete(record)
177
185
  record.destroy if method.to_sym == :destroy
@@ -179,17 +187,29 @@ module MassiveRecord
179
187
  end
180
188
 
181
189
  proxy_owner.save if proxy_owner.persisted?
190
+ removed
182
191
  end
183
192
 
184
193
 
185
194
 
186
- def find_proxy_target(ids = nil)
187
- ids = proxy_owner.send(metadata.foreign_key) if ids.nil?
195
+ def find_proxy_target(options = {})
196
+ ids = options.delete(:ids) || proxy_owner.send(metadata.foreign_key)
197
+ unsupported_finder_options = UnsupportedFinderOption::OPTIONS & options.keys.collect(&:to_s)
198
+
199
+ if unsupported_finder_options.any?
200
+ raise UnsupportedFinderOption.new(
201
+ <<-TXT
202
+ Sorry, option(s): #{unsupported_finder_options.join(', ')} are not supported when foreign
203
+ keys are persisted in proxy owner #{proxy_owner.class}
204
+ TXT
205
+ )
206
+ end
207
+
188
208
  proxy_target_class.find(ids, :skip_expected_result_check => true)
189
209
  end
190
210
 
191
- def find_proxy_target_with_proc(options = {})
192
- [super].compact.flatten
211
+ def find_proxy_target_with_proc(options = {}, &block)
212
+ Array(super).compact
193
213
  end
194
214
 
195
215
  def can_find_proxy_target?
@@ -215,7 +235,11 @@ module MassiveRecord
215
235
  end
216
236
 
217
237
  def foreign_key_in_proxy_owner_exists?(id)
218
- proxy_owner.send(metadata.foreign_key).include? id
238
+ foreign_keys_in_proxy_owner.include? id
239
+ end
240
+
241
+ def foreign_keys_in_proxy_owner
242
+ proxy_owner.send(metadata.foreign_key)
219
243
  end
220
244
 
221
245
  def notify_of_change_in_proxy_owner_foreign_key