activerecord 1.0.0 → 3.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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,403 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/enumerable'
3
+
4
+ module ActiveRecord
5
+ # See ActiveRecord::AssociationPreload::ClassMethods for documentation.
6
+ module AssociationPreload #:nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ # Implements the details of eager loading of Active Record associations.
10
+ # Application developers should not use this module directly.
11
+ #
12
+ # <tt>ActiveRecord::Base</tt> is extended with this module. The source code in
13
+ # <tt>ActiveRecord::Base</tt> references methods defined in this module.
14
+ #
15
+ # Note that 'eager loading' and 'preloading' are actually the same thing.
16
+ # However, there are two different eager loading strategies.
17
+ #
18
+ # The first one is by using table joins. This was only strategy available
19
+ # prior to Rails 2.1. Suppose that you have an Author model with columns
20
+ # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
21
+ # this strategy, Active Record would try to retrieve all data for an author
22
+ # and all of its books via a single query:
23
+ #
24
+ # SELECT * FROM authors
25
+ # LEFT OUTER JOIN books ON authors.id = books.id
26
+ # WHERE authors.name = 'Ken Akamatsu'
27
+ #
28
+ # However, this could result in many rows that contain redundant data. After
29
+ # having received the first row, we already have enough data to instantiate
30
+ # the Author object. In all subsequent rows, only the data for the joined
31
+ # 'books' table is useful; the joined 'authors' data is just redundant, and
32
+ # processing this redundant data takes memory and CPU time. The problem
33
+ # quickly becomes worse and worse as the level of eager loading increases
34
+ # (i.e. if Active Record is to eager load the associations' associations as
35
+ # well).
36
+ #
37
+ # The second strategy is to use multiple database queries, one for each
38
+ # level of association. Since Rails 2.1, this is the default strategy. In
39
+ # situations where a table join is necessary (e.g. when the +:conditions+
40
+ # option references an association's column), it will fallback to the table
41
+ # join strategy.
42
+ #
43
+ # See also ActiveRecord::Associations::ClassMethods, which explains eager
44
+ # loading in a more high-level (application developer-friendly) manner.
45
+ module ClassMethods
46
+ protected
47
+
48
+ # Eager loads the named associations for the given Active Record record(s).
49
+ #
50
+ # In this description, 'association name' shall refer to the name passed
51
+ # to an association creation method. For example, a model that specifies
52
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
53
+ # names +:author+ and +:buyers+.
54
+ #
55
+ # == Parameters
56
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
57
+ # i.e. +records+ itself may also contain arrays of records. In any case,
58
+ # +preload_associations+ will preload the all associations records by
59
+ # flattening +records+.
60
+ #
61
+ # +associations+ specifies one or more associations that you want to
62
+ # preload. It may be:
63
+ # - a Symbol or a String which specifies a single association name. For
64
+ # example, specifying +:books+ allows this method to preload all books
65
+ # for an Author.
66
+ # - an Array which specifies multiple association names. This array
67
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
68
+ # allows this method to preload an author's avatar as well as all of his
69
+ # books.
70
+ # - a Hash which specifies multiple association names, as well as
71
+ # association names for the to-be-preloaded association objects. For
72
+ # example, specifying <tt>{ :author => :avatar }</tt> will preload a
73
+ # book's author, as well as that author's avatar.
74
+ #
75
+ # +:associations+ has the same format as the +:include+ option for
76
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
77
+ #
78
+ # :books
79
+ # [ :books, :author ]
80
+ # { :author => :avatar }
81
+ # [ :books, { :author => :avatar } ]
82
+ #
83
+ # +preload_options+ contains options that will be passed to ActiveRecord::Base#find
84
+ # (which is called under the hood for preloading records). But it is passed
85
+ # only one level deep in the +associations+ argument, i.e. it's not passed
86
+ # to the child associations when +associations+ is a Hash.
87
+ def preload_associations(records, associations, preload_options={})
88
+ records = Array.wrap(records).compact.uniq
89
+ return if records.empty?
90
+ case associations
91
+ when Array then associations.each {|association| preload_associations(records, association, preload_options)}
92
+ when Symbol, String then preload_one_association(records, associations.to_sym, preload_options)
93
+ when Hash then
94
+ associations.each do |parent, child|
95
+ raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
96
+ preload_associations(records, parent, preload_options)
97
+ reflection = reflections[parent]
98
+ parents = records.sum { |record| Array.wrap(record.send(reflection.name)) }
99
+ unless parents.empty?
100
+ parents.first.class.preload_associations(parents, child)
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Preloads a specific named association for the given records. This is
109
+ # called by +preload_associations+ as its base case.
110
+ def preload_one_association(records, association, preload_options={})
111
+ class_to_reflection = {}
112
+ # Not all records have the same class, so group then preload
113
+ # group on the reflection itself so that if various subclass share the same association then
114
+ # we do not split them unnecessarily
115
+ records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records|
116
+ raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
117
+
118
+ # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
119
+ # the following could call 'preload_belongs_to_association',
120
+ # 'preload_has_many_association', etc.
121
+ send("preload_#{reflection.macro}_association", _records, reflection, preload_options)
122
+ end
123
+ end
124
+
125
+ def add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
126
+ parent_records.each do |parent_record|
127
+ association_proxy = parent_record.send(reflection_name)
128
+ association_proxy.loaded
129
+ association_proxy.target.push(*Array.wrap(associated_record))
130
+
131
+ association_proxy.__send__(:set_inverse_instance, associated_record, parent_record)
132
+ end
133
+ end
134
+
135
+ def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
136
+ parent_records.each do |parent_record|
137
+ parent_record.send("set_#{reflection_name}_target", associated_record)
138
+ end
139
+ end
140
+
141
+ def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
142
+ associated_records.each do |associated_record|
143
+ mapped_records = id_to_record_map[associated_record[key].to_s]
144
+ add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
145
+ end
146
+ end
147
+
148
+ def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
149
+ seen_keys = {}
150
+ associated_records.each do |associated_record|
151
+ #this is a has_one or belongs_to: there should only be one record.
152
+ #Unfortunately we can't (in portable way) ask the database for
153
+ #'all records where foo_id in (x,y,z), but please
154
+ # only one row per distinct foo_id' so this where we enforce that
155
+ next if seen_keys[associated_record[key].to_s]
156
+ seen_keys[associated_record[key].to_s] = true
157
+ mapped_records = id_to_record_map[associated_record[key].to_s]
158
+ mapped_records.each do |mapped_record|
159
+ association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record)
160
+ association_proxy.__send__(:set_inverse_instance, associated_record, mapped_record)
161
+ end
162
+ end
163
+
164
+ id_to_record_map.each do |id, records|
165
+ next if seen_keys.include?(id.to_s)
166
+ records.each {|record| record.send("set_#{reflection_name}_target", nil) }
167
+ end
168
+ end
169
+
170
+ # Given a collection of Active Record objects, constructs a Hash which maps
171
+ # the objects' IDs to the relevant objects. Returns a 2-tuple
172
+ # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
173
+ # and +ids+ is an Array of record IDs.
174
+ def construct_id_map(records, primary_key=nil)
175
+ id_to_record_map = {}
176
+ ids = []
177
+ records.each do |record|
178
+ primary_key ||= record.class.primary_key
179
+ ids << record[primary_key]
180
+ mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
181
+ mapped_records << record
182
+ end
183
+ ids.uniq!
184
+ return id_to_record_map, ids
185
+ end
186
+
187
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
188
+ table_name = reflection.klass.quoted_table_name
189
+ id_to_record_map, ids = construct_id_map(records)
190
+ records.each {|record| record.send(reflection.name).loaded}
191
+ options = reflection.options
192
+
193
+ conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
194
+ conditions << append_conditions(reflection, preload_options)
195
+
196
+ associated_records = reflection.klass.unscoped.where([conditions, ids]).
197
+ includes(options[:include]).
198
+ joins("INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}").
199
+ select("#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id").
200
+ order(options[:order]).to_a
201
+
202
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
203
+ end
204
+
205
+ def preload_has_one_association(records, reflection, preload_options={})
206
+ return if records.first.send("loaded_#{reflection.name}?")
207
+ id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
208
+ options = reflection.options
209
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
210
+ if options[:through]
211
+ through_records = preload_through_records(records, reflection, options[:through])
212
+ through_reflection = reflections[options[:through]]
213
+ through_primary_key = through_reflection.primary_key_name
214
+ unless through_records.empty?
215
+ source = reflection.source_reflection.name
216
+ through_records.first.class.preload_associations(through_records, source)
217
+ if through_reflection.macro == :belongs_to
218
+ rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
219
+ rev_primary_key = through_reflection.klass.primary_key
220
+ through_records.each do |through_record|
221
+ add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
222
+ reflection.name, through_record.send(source))
223
+ end
224
+ else
225
+ through_records.each do |through_record|
226
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
227
+ reflection.name, through_record.send(source))
228
+ end
229
+ end
230
+ end
231
+ else
232
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
233
+ end
234
+ end
235
+
236
+ def preload_has_many_association(records, reflection, preload_options={})
237
+ return if records.first.send(reflection.name).loaded?
238
+ options = reflection.options
239
+
240
+ primary_key_name = reflection.through_reflection_primary_key_name
241
+ id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
242
+ records.each {|record| record.send(reflection.name).loaded}
243
+
244
+ if options[:through]
245
+ through_records = preload_through_records(records, reflection, options[:through])
246
+ through_reflection = reflections[options[:through]]
247
+ unless through_records.empty?
248
+ source = reflection.source_reflection.name
249
+ through_records.first.class.preload_associations(through_records, source, options)
250
+ through_records.each do |through_record|
251
+ through_record_id = through_record[reflection.through_reflection_primary_key].to_s
252
+ add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source))
253
+ end
254
+ end
255
+
256
+ else
257
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
258
+ reflection.primary_key_name)
259
+ end
260
+ end
261
+
262
+ def preload_through_records(records, reflection, through_association)
263
+ through_reflection = reflections[through_association]
264
+ through_primary_key = through_reflection.primary_key_name
265
+
266
+ through_records = []
267
+ if reflection.options[:source_type]
268
+ interface = reflection.source_reflection.options[:foreign_type]
269
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
270
+
271
+ records.compact!
272
+ records.first.class.preload_associations(records, through_association, preload_options)
273
+
274
+ # Dont cache the association - we would only be caching a subset
275
+ records.each do |record|
276
+ proxy = record.send(through_association)
277
+
278
+ if proxy.respond_to?(:target)
279
+ through_records.concat Array.wrap(proxy.target)
280
+ proxy.reset
281
+ else # this is a has_one :through reflection
282
+ through_records << proxy if proxy
283
+ end
284
+ end
285
+ else
286
+ options = {}
287
+ options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
288
+ options[:order] = reflection.options[:order]
289
+ options[:conditions] = reflection.options[:conditions]
290
+ records.first.class.preload_associations(records, through_association, options)
291
+
292
+ records.each do |record|
293
+ through_records.concat Array.wrap(record.send(through_association))
294
+ end
295
+ end
296
+ through_records
297
+ end
298
+
299
+ def preload_belongs_to_association(records, reflection, preload_options={})
300
+ return if records.first.send("loaded_#{reflection.name}?")
301
+ options = reflection.options
302
+ primary_key_name = reflection.primary_key_name
303
+
304
+ if options[:polymorphic]
305
+ polymorph_type = options[:foreign_type]
306
+ klasses_and_ids = {}
307
+
308
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back
309
+ # to their parent_records
310
+ records.each do |record|
311
+ if klass = record.send(polymorph_type)
312
+ klass_id = record.send(primary_key_name)
313
+ if klass_id
314
+ id_map = klasses_and_ids[klass] ||= {}
315
+ id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
316
+ id_list_for_klass_id << record
317
+ end
318
+ end
319
+ end
320
+ klasses_and_ids = klasses_and_ids.to_a
321
+ else
322
+ id_map = {}
323
+ records.each do |record|
324
+ key = record.send(primary_key_name)
325
+ if key
326
+ mapped_records = (id_map[key.to_s] ||= [])
327
+ mapped_records << record
328
+ end
329
+ end
330
+ klasses_and_ids = [[reflection.klass.name, id_map]]
331
+ end
332
+
333
+ klasses_and_ids.each do |klass_and_id|
334
+ klass_name, id_map = *klass_and_id
335
+ next if id_map.empty?
336
+ klass = klass_name.constantize
337
+
338
+ table_name = klass.quoted_table_name
339
+ primary_key = reflection.options[:primary_key] || klass.primary_key
340
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
341
+
342
+ ids = id_map.keys.map do |id|
343
+ if column_type == :integer
344
+ id.to_i
345
+ elsif column_type == :float
346
+ id.to_f
347
+ else
348
+ id
349
+ end
350
+ end
351
+
352
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
353
+ conditions << append_conditions(reflection, preload_options)
354
+
355
+ associated_records = klass.unscoped.where([conditions, ids]).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
356
+
357
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
358
+ end
359
+ end
360
+
361
+ def find_associated_records(ids, reflection, preload_options)
362
+ options = reflection.options
363
+ table_name = reflection.klass.quoted_table_name
364
+
365
+ if interface = reflection.options[:as]
366
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
367
+ else
368
+ foreign_key = reflection.primary_key_name
369
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
370
+ end
371
+
372
+ conditions << append_conditions(reflection, preload_options)
373
+
374
+ find_options = {
375
+ :select => preload_options[:select] || options[:select] || Arel::SqlLiteral.new("#{table_name}.*"),
376
+ :include => preload_options[:include] || options[:include],
377
+ :conditions => [conditions, ids],
378
+ :joins => options[:joins],
379
+ :group => preload_options[:group] || options[:group],
380
+ :order => preload_options[:order] || options[:order]
381
+ }
382
+
383
+ reflection.klass.scoped.apply_finder_options(find_options).to_a
384
+ end
385
+
386
+
387
+ def interpolate_sql_for_preload(sql)
388
+ instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
389
+ end
390
+
391
+ def append_conditions(reflection, preload_options)
392
+ sql = ""
393
+ sql << " AND (#{interpolate_sql_for_preload(reflection.sanitized_conditions)})" if reflection.sanitized_conditions
394
+ sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]
395
+ sql
396
+ end
397
+
398
+ def in_or_equals_for_ids(ids)
399
+ ids.size > 1 ? "IN (?)" : "= ?"
400
+ end
401
+ end
402
+ end
403
+ end