activerecord 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,562 +0,0 @@
1
- require 'set'
2
- require 'active_support/core_ext/array/wrap'
3
-
4
- module ActiveRecord
5
- module Associations
6
- # = Active Record Association Collection
7
- #
8
- # AssociationCollection is an abstract class that provides common stuff to
9
- # ease the implementation of association proxies that represent
10
- # collections. See the class hierarchy in AssociationProxy.
11
- #
12
- # You need to be careful with assumptions regarding the target: The proxy
13
- # does not fetch records from the database until it needs them, but new
14
- # ones created with +build+ are added to the target. So, the target may be
15
- # non-empty and still lack children waiting to be read from the database.
16
- # If you look directly to the database you cannot assume that's the entire
17
- # collection because new records may have been added to the target, etc.
18
- #
19
- # If you need to work on all current children, new and existing records,
20
- # +load_target+ and the +loaded+ flag are your friends.
21
- class AssociationCollection < AssociationProxy #:nodoc:
22
- def initialize(owner, reflection)
23
- super
24
- construct_sql
25
- end
26
-
27
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
28
-
29
- def select(select = nil)
30
- if block_given?
31
- load_target
32
- @target.select.each { |e| yield e }
33
- else
34
- scoped.select(select)
35
- end
36
- end
37
-
38
- def scoped
39
- with_scope(construct_scope) { @reflection.klass.scoped }
40
- end
41
-
42
- def find(*args)
43
- options = args.extract_options!
44
-
45
- # If using a custom finder_sql, scan the entire collection.
46
- if @reflection.options[:finder_sql]
47
- expects_array = args.first.kind_of?(Array)
48
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
49
-
50
- if ids.size == 1
51
- id = ids.first
52
- record = load_target.detect { |r| id == r.id }
53
- expects_array ? [ record ] : record
54
- else
55
- load_target.select { |r| ids.include?(r.id) }
56
- end
57
- else
58
- merge_options_from_reflection!(options)
59
- construct_find_options!(options)
60
-
61
- find_scope = construct_scope[:find].slice(:conditions, :order)
62
-
63
- with_scope(:find => find_scope) do
64
- relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
65
-
66
- case args.first
67
- when :first, :last
68
- relation.send(args.first)
69
- when :all
70
- records = relation.all
71
- @reflection.options[:uniq] ? uniq(records) : records
72
- else
73
- relation.find(*args)
74
- end
75
- end
76
- end
77
- end
78
-
79
- # Fetches the first one using SQL if possible.
80
- def first(*args)
81
- if fetch_first_or_last_using_find?(args)
82
- find(:first, *args)
83
- else
84
- load_target unless loaded?
85
- @target.first(*args)
86
- end
87
- end
88
-
89
- # Fetches the last one using SQL if possible.
90
- def last(*args)
91
- if fetch_first_or_last_using_find?(args)
92
- find(:last, *args)
93
- else
94
- load_target unless loaded?
95
- @target.last(*args)
96
- end
97
- end
98
-
99
- def to_ary
100
- load_target
101
- if @target.is_a?(Array)
102
- @target.to_ary
103
- else
104
- Array.wrap(@target)
105
- end
106
- end
107
- alias_method :to_a, :to_ary
108
-
109
- def reset
110
- reset_target!
111
- reset_named_scopes_cache!
112
- @loaded = false
113
- end
114
-
115
- def build(attributes = {}, &block)
116
- if attributes.is_a?(Array)
117
- attributes.collect { |attr| build(attr, &block) }
118
- else
119
- build_record(attributes) do |record|
120
- block.call(record) if block_given?
121
- set_belongs_to_association_for(record)
122
- end
123
- end
124
- end
125
-
126
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
127
- # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
128
- def <<(*records)
129
- result = true
130
- load_target if @owner.new_record?
131
-
132
- transaction do
133
- flatten_deeper(records).each do |record|
134
- raise_on_type_mismatch(record)
135
- add_record_to_target_with_callbacks(record) do |r|
136
- result &&= insert_record(record) unless @owner.new_record?
137
- end
138
- end
139
- end
140
-
141
- result && self
142
- end
143
-
144
- alias_method :push, :<<
145
- alias_method :concat, :<<
146
-
147
- # Starts a transaction in the association class's database connection.
148
- #
149
- # class Author < ActiveRecord::Base
150
- # has_many :books
151
- # end
152
- #
153
- # Author.first.books.transaction do
154
- # # same effect as calling Book.transaction
155
- # end
156
- def transaction(*args)
157
- @reflection.klass.transaction(*args) do
158
- yield
159
- end
160
- end
161
-
162
- # Remove all records from this association
163
- #
164
- # See delete for more info.
165
- def delete_all
166
- load_target
167
- delete(@target)
168
- reset_target!
169
- reset_named_scopes_cache!
170
- end
171
-
172
- # Calculate sum using SQL, not Enumerable
173
- def sum(*args)
174
- if block_given?
175
- calculate(:sum, *args) { |*block_args| yield(*block_args) }
176
- else
177
- calculate(:sum, *args)
178
- end
179
- end
180
-
181
- # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
182
- # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
183
- # descendant's +construct_sql+ method will have set :counter_sql automatically.
184
- # Otherwise, construct options and pass them with scope to the target class's +count+.
185
- def count(column_name = nil, options = {})
186
- column_name, options = nil, column_name if column_name.is_a?(Hash)
187
-
188
- if @reflection.options[:counter_sql] && !options.blank?
189
- raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
190
- elsif @reflection.options[:counter_sql]
191
- @reflection.klass.count_by_sql(@counter_sql)
192
- else
193
-
194
- if @reflection.options[:uniq]
195
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
196
- column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
197
- options.merge!(:distinct => true)
198
- end
199
-
200
- value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
201
-
202
- limit = @reflection.options[:limit]
203
- offset = @reflection.options[:offset]
204
-
205
- if limit || offset
206
- [ [value - offset.to_i, 0].max, limit.to_i ].min
207
- else
208
- value
209
- end
210
- end
211
- end
212
-
213
- # Removes +records+ from this association calling +before_remove+ and
214
- # +after_remove+ callbacks.
215
- #
216
- # This method is abstract in the sense that +delete_records+ has to be
217
- # provided by descendants. Note this method does not imply the records
218
- # are actually removed from the database, that depends precisely on
219
- # +delete_records+. They are in any case removed from the collection.
220
- def delete(*records)
221
- remove_records(records) do |_records, old_records|
222
- delete_records(old_records) if old_records.any?
223
- _records.each { |record| @target.delete(record) }
224
- end
225
- end
226
-
227
- # Destroy +records+ and remove them from this association calling
228
- # +before_remove+ and +after_remove+ callbacks.
229
- #
230
- # Note that this method will _always_ remove records from the database
231
- # ignoring the +:dependent+ option.
232
- def destroy(*records)
233
- records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
234
- remove_records(records) do |_records, old_records|
235
- old_records.each { |record| record.destroy }
236
- end
237
-
238
- load_target
239
- end
240
-
241
- # Removes all records from this association. Returns +self+ so method calls may be chained.
242
- def clear
243
- return self if length.zero? # forces load_target if it hasn't happened already
244
-
245
- if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
246
- destroy_all
247
- else
248
- delete_all
249
- end
250
-
251
- self
252
- end
253
-
254
- # Destroy all the records from this association.
255
- #
256
- # See destroy for more info.
257
- def destroy_all
258
- load_target
259
- destroy(@target).tap do
260
- reset_target!
261
- reset_named_scopes_cache!
262
- end
263
- end
264
-
265
- def create(attrs = {})
266
- if attrs.is_a?(Array)
267
- attrs.collect { |attr| create(attr) }
268
- else
269
- create_record(attrs) do |record|
270
- yield(record) if block_given?
271
- record.save
272
- end
273
- end
274
- end
275
-
276
- def create!(attrs = {})
277
- create_record(attrs) do |record|
278
- yield(record) if block_given?
279
- record.save!
280
- end
281
- end
282
-
283
- # Returns the size of the collection by executing a SELECT COUNT(*)
284
- # query if the collection hasn't been loaded, and calling
285
- # <tt>collection.size</tt> if it has.
286
- #
287
- # If the collection has been already loaded +size+ and +length+ are
288
- # equivalent. If not and you are going to need the records anyway
289
- # +length+ will take one less query. Otherwise +size+ is more efficient.
290
- #
291
- # This method is abstract in the sense that it relies on
292
- # +count_records+, which is a method descendants have to provide.
293
- def size
294
- if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
295
- @target.size
296
- elsif !loaded? && @reflection.options[:group]
297
- load_target.size
298
- elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
299
- unsaved_records = @target.select { |r| r.new_record? }
300
- unsaved_records.size + count_records
301
- else
302
- count_records
303
- end
304
- end
305
-
306
- # Returns the size of the collection calling +size+ on the target.
307
- #
308
- # If the collection has been already loaded +length+ and +size+ are
309
- # equivalent. If not and you are going to need the records anyway this
310
- # method will take one less query. Otherwise +size+ is more efficient.
311
- def length
312
- load_target.size
313
- end
314
-
315
- # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
316
- # not been already loaded and you are going to fetch the records anyway
317
- # it is better to check <tt>collection.length.zero?</tt>.
318
- def empty?
319
- size.zero?
320
- end
321
-
322
- def any?
323
- if block_given?
324
- method_missing(:any?) { |*block_args| yield(*block_args) }
325
- else
326
- !empty?
327
- end
328
- end
329
-
330
- # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
331
- def many?
332
- if block_given?
333
- method_missing(:many?) { |*block_args| yield(*block_args) }
334
- else
335
- size > 1
336
- end
337
- end
338
-
339
- def uniq(collection = self)
340
- seen = Set.new
341
- collection.inject([]) do |kept, record|
342
- unless seen.include?(record.id)
343
- kept << record
344
- seen << record.id
345
- end
346
- kept
347
- end
348
- end
349
-
350
- # Replace this collection with +other_array+
351
- # This will perform a diff and delete/add only records that have changed.
352
- def replace(other_array)
353
- other_array.each { |val| raise_on_type_mismatch(val) }
354
-
355
- load_target
356
- other = other_array.size < 100 ? other_array : other_array.to_set
357
- current = @target.size < 100 ? @target : @target.to_set
358
-
359
- transaction do
360
- delete(@target.select { |v| !other.include?(v) })
361
- concat(other_array.select { |v| !current.include?(v) })
362
- end
363
- end
364
-
365
- def include?(record)
366
- return false unless record.is_a?(@reflection.klass)
367
- load_target if @reflection.options[:finder_sql] && !loaded?
368
- return @target.include?(record) if loaded?
369
- exists?(record)
370
- end
371
-
372
- def proxy_respond_to?(method, include_private = false)
373
- super || @reflection.klass.respond_to?(method, include_private)
374
- end
375
-
376
- protected
377
- def construct_find_options!(options)
378
- end
379
-
380
- def construct_counter_sql
381
- if @reflection.options[:counter_sql]
382
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
383
- elsif @reflection.options[:finder_sql]
384
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
385
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
386
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
387
- else
388
- @counter_sql = @finder_sql
389
- end
390
- end
391
-
392
- def load_target
393
- if !@owner.new_record? || foreign_key_present
394
- begin
395
- if !loaded?
396
- if @target.is_a?(Array) && @target.any?
397
- @target = find_target.map do |f|
398
- i = @target.index(f)
399
- if i
400
- @target.delete_at(i).tap do |t|
401
- keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
402
- t.attributes = f.attributes.except(*keys)
403
- end
404
- else
405
- f
406
- end
407
- end + @target
408
- else
409
- @target = find_target
410
- end
411
- end
412
- rescue ActiveRecord::RecordNotFound
413
- reset
414
- end
415
- end
416
-
417
- loaded if target
418
- target
419
- end
420
-
421
- def method_missing(method, *args)
422
- match = DynamicFinderMatch.match(method)
423
- if match && match.creator?
424
- attributes = match.attribute_names
425
- return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
426
- end
427
-
428
- if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
429
- if block_given?
430
- super { |*block_args| yield(*block_args) }
431
- else
432
- super
433
- end
434
- elsif @reflection.klass.scopes[method]
435
- @_named_scopes_cache ||= {}
436
- @_named_scopes_cache[method] ||= {}
437
- @_named_scopes_cache[method][args] ||= with_scope(construct_scope) { @reflection.klass.send(method, *args) }
438
- else
439
- with_scope(construct_scope) do
440
- if block_given?
441
- @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
442
- else
443
- @reflection.klass.send(method, *args)
444
- end
445
- end
446
- end
447
- end
448
-
449
- # overloaded in derived Association classes to provide useful scoping depending on association type.
450
- def construct_scope
451
- {}
452
- end
453
-
454
- def reset_target!
455
- @target = Array.new
456
- end
457
-
458
- def reset_named_scopes_cache!
459
- @_named_scopes_cache = {}
460
- end
461
-
462
- def find_target
463
- records =
464
- if @reflection.options[:finder_sql]
465
- @reflection.klass.find_by_sql(@finder_sql)
466
- else
467
- find(:all)
468
- end
469
-
470
- records = @reflection.options[:uniq] ? uniq(records) : records
471
- records.each do |record|
472
- set_inverse_instance(record, @owner)
473
- end
474
- records
475
- end
476
-
477
- def add_record_to_target_with_callbacks(record)
478
- callback(:before_add, record)
479
- yield(record) if block_given?
480
- @target ||= [] unless loaded?
481
- if index = @target.index(record)
482
- @target[index] = record
483
- else
484
- @target << record
485
- end
486
- callback(:after_add, record)
487
- set_inverse_instance(record, @owner)
488
- record
489
- end
490
-
491
- private
492
- def create_record(attrs)
493
- attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
494
- ensure_owner_is_not_new
495
-
496
- _scope = self.construct_scope[:create]
497
- csm = @reflection.klass.send(:current_scoped_methods)
498
- options = (csm.blank? || !_scope.is_a?(Hash)) ? _scope : _scope.merge(csm.where_values_hash)
499
-
500
- record = @reflection.klass.send(:with_scope, :create => options) do
501
- @reflection.build_association(attrs)
502
- end
503
- if block_given?
504
- add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
505
- else
506
- add_record_to_target_with_callbacks(record)
507
- end
508
- end
509
-
510
- def build_record(attrs)
511
- attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
512
- record = @reflection.build_association(attrs)
513
- if block_given?
514
- add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
515
- else
516
- add_record_to_target_with_callbacks(record)
517
- end
518
- end
519
-
520
- def remove_records(*records)
521
- records = flatten_deeper(records)
522
- records.each { |record| raise_on_type_mismatch(record) }
523
-
524
- transaction do
525
- records.each { |record| callback(:before_remove, record) }
526
- old_records = records.reject { |r| r.new_record? }
527
- yield(records, old_records)
528
- records.each { |record| callback(:after_remove, record) }
529
- end
530
- end
531
-
532
- def callback(method, record)
533
- callbacks_for(method).each do |callback|
534
- case callback
535
- when Symbol
536
- @owner.send(callback, record)
537
- when Proc
538
- callback.call(@owner, record)
539
- else
540
- callback.send(method, @owner, record)
541
- end
542
- end
543
- end
544
-
545
- def callbacks_for(callback_name)
546
- full_callback_name = "#{callback_name}_for_#{@reflection.name}"
547
- @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
548
- end
549
-
550
- def ensure_owner_is_not_new
551
- if @owner.new_record?
552
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
553
- end
554
- end
555
-
556
- def fetch_first_or_last_using_find?(args)
557
- args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
558
- @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
559
- end
560
- end
561
- end
562
- end