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
@@ -0,0 +1,235 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
5
+ autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
6
+ autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
7
+
8
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
9
+
10
+ # base is the base class on which operation is taking place.
11
+ # associations is the list of associations which are joined using hash, symbol or array.
12
+ # joins is the list of all string join commnads and arel nodes.
13
+ #
14
+ # Example :
15
+ #
16
+ # class Physician < ActiveRecord::Base
17
+ # has_many :appointments
18
+ # has_many :patients, through: :appointments
19
+ # end
20
+ #
21
+ # If I execute `@physician.patients.to_a` then
22
+ # base #=> Physician
23
+ # associations #=> []
24
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
25
+ #
26
+ # However if I execute `Physician.joins(:appointments).to_a` then
27
+ # base #=> Physician
28
+ # associations #=> [:appointments]
29
+ # joins #=> []
30
+ #
31
+ def initialize(base, associations, joins)
32
+ @base_klass = base
33
+ @table_joins = joins
34
+ @join_parts = [JoinBase.new(base)]
35
+ @associations = {}
36
+ @reflections = []
37
+ @alias_tracker = AliasTracker.new(base.connection, joins)
38
+ @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
39
+ build(associations)
40
+ end
41
+
42
+ def graft(*associations)
43
+ associations.each do |association|
44
+ join_associations.detect {|a| association == a} ||
45
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
46
+ end
47
+ self
48
+ end
49
+
50
+ def join_associations
51
+ join_parts.last(join_parts.length - 1)
52
+ end
53
+
54
+ def join_base
55
+ join_parts.first
56
+ end
57
+
58
+ def columns
59
+ join_parts.collect { |join_part|
60
+ table = join_part.aliased_table
61
+ join_part.column_names_with_alias.collect{ |column_name, aliased_name|
62
+ table[column_name].as Arel.sql(aliased_name)
63
+ }
64
+ }.flatten
65
+ end
66
+
67
+ def instantiate(rows)
68
+ primary_key = join_base.aliased_primary_key
69
+ parents = {}
70
+
71
+ records = rows.map { |model|
72
+ primary_id = model[primary_key]
73
+ parent = parents[primary_id] ||= join_base.instantiate(model)
74
+ construct(parent, @associations, join_associations, model)
75
+ parent
76
+ }.uniq
77
+
78
+ remove_duplicate_results!(base_klass, records, @associations)
79
+ records
80
+ end
81
+
82
+ protected
83
+
84
+ def remove_duplicate_results!(base, records, associations)
85
+ case associations
86
+ when Symbol, String
87
+ reflection = base.reflections[associations]
88
+ remove_uniq_by_reflection(reflection, records)
89
+ when Array
90
+ associations.each do |association|
91
+ remove_duplicate_results!(base, records, association)
92
+ end
93
+ when Hash
94
+ associations.each_key do |name|
95
+ reflection = base.reflections[name]
96
+ remove_uniq_by_reflection(reflection, records)
97
+
98
+ parent_records = []
99
+ records.each do |record|
100
+ if descendant = record.send(reflection.name)
101
+ if reflection.collection?
102
+ parent_records.concat descendant.target.uniq
103
+ else
104
+ parent_records << descendant
105
+ end
106
+ end
107
+ end
108
+
109
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
110
+ end
111
+ end
112
+ end
113
+
114
+ def cache_joined_association(association)
115
+ associations = []
116
+ parent = association.parent
117
+ while parent != join_base
118
+ associations.unshift(parent.reflection.name)
119
+ parent = parent.parent
120
+ end
121
+ ref = @associations
122
+ associations.each do |key|
123
+ ref = ref[key]
124
+ end
125
+ ref[association.reflection.name] ||= {}
126
+ end
127
+
128
+ def build(associations, parent = nil, join_type = Arel::InnerJoin)
129
+ parent ||= join_parts.last
130
+ case associations
131
+ when Symbol, String
132
+ reflection = parent.reflections[associations.intern] or
133
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
134
+ unless join_association = find_join_association(reflection, parent)
135
+ @reflections << reflection
136
+ join_association = build_join_association(reflection, parent)
137
+ join_association.join_type = join_type
138
+ @join_parts << join_association
139
+ cache_joined_association(join_association)
140
+ end
141
+ join_association
142
+ when Array
143
+ associations.each do |association|
144
+ build(association, parent, join_type)
145
+ end
146
+ when Hash
147
+ associations.keys.sort_by { |a| a.to_s }.each do |name|
148
+ join_association = build(name, parent, join_type)
149
+ build(associations[name], join_association, join_type)
150
+ end
151
+ else
152
+ raise ConfigurationError, associations.inspect
153
+ end
154
+ end
155
+
156
+ def find_join_association(name_or_reflection, parent)
157
+ if String === name_or_reflection
158
+ name_or_reflection = name_or_reflection.to_sym
159
+ end
160
+
161
+ join_associations.detect { |j|
162
+ j.reflection == name_or_reflection && j.parent == parent
163
+ }
164
+ end
165
+
166
+ def remove_uniq_by_reflection(reflection, records)
167
+ if reflection && reflection.collection?
168
+ records.each { |record| record.send(reflection.name).target.uniq! }
169
+ end
170
+ end
171
+
172
+ def build_join_association(reflection, parent)
173
+ JoinAssociation.new(reflection, self, parent)
174
+ end
175
+
176
+ def construct(parent, associations, join_parts, row)
177
+ case associations
178
+ when Symbol, String
179
+ name = associations.to_s
180
+
181
+ join_part = join_parts.detect { |j|
182
+ j.reflection.name.to_s == name &&
183
+ j.parent_table_name == parent.class.table_name }
184
+
185
+ raise(ConfigurationError, "No such association") unless join_part
186
+
187
+ join_parts.delete(join_part)
188
+ construct_association(parent, join_part, row)
189
+ when Array
190
+ associations.each do |association|
191
+ construct(parent, association, join_parts, row)
192
+ end
193
+ when Hash
194
+ associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
195
+ association = construct(parent, association_name, join_parts, row)
196
+ construct(association, assoc, join_parts, row) if association
197
+ end
198
+ else
199
+ raise ConfigurationError, associations.inspect
200
+ end
201
+ end
202
+
203
+ def construct_association(record, join_part, row)
204
+ return if record.id.to_s != join_part.parent.record_id(row).to_s
205
+
206
+ macro = join_part.reflection.macro
207
+ if macro == :has_one
208
+ return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
209
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
210
+ set_target_and_inverse(join_part, association, record)
211
+ else
212
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
213
+ case macro
214
+ when :has_many, :has_and_belongs_to_many
215
+ other = record.association(join_part.reflection.name)
216
+ other.loaded!
217
+ other.target.push(association) if association
218
+ other.set_inverse_instance(association)
219
+ when :belongs_to
220
+ set_target_and_inverse(join_part, association, record)
221
+ else
222
+ raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
223
+ end
224
+ end
225
+ association
226
+ end
227
+
228
+ def set_target_and_inverse(join_part, association, record)
229
+ other = record.association(join_part.reflection.name)
230
+ other.target = association
231
+ other.set_inverse_instance(association)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
4
+ module JoinHelper #:nodoc:
5
+
6
+ def join_type
7
+ Arel::InnerJoin
8
+ end
9
+
10
+ private
11
+
12
+ def construct_tables
13
+ tables = []
14
+ chain.each do |reflection|
15
+ tables << alias_tracker.aliased_table_for(
16
+ table_name_for(reflection),
17
+ table_alias_for(reflection, reflection != self.reflection)
18
+ )
19
+
20
+ if reflection.source_macro == :has_and_belongs_to_many
21
+ tables << alias_tracker.aliased_table_for(
22
+ (reflection.source_reflection || reflection).join_table,
23
+ table_alias_for(reflection, true)
24
+ )
25
+ end
26
+ end
27
+ tables
28
+ end
29
+
30
+ def table_name_for(reflection)
31
+ reflection.table_name
32
+ end
33
+
34
+ def table_alias_for(reflection, join = false)
35
+ name = "#{reflection.plural_name}_#{alias_suffix}"
36
+ name << "_join" if join
37
+ name
38
+ end
39
+
40
+ def join(table, constraint)
41
+ table.create_join(table, table.create_on(constraint), join_type)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,121 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class Association #:nodoc:
5
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
6
+
7
+ def initialize(klass, owners, reflection, preload_scope)
8
+ @klass = klass
9
+ @owners = owners
10
+ @reflection = reflection
11
+ @preload_scope = preload_scope
12
+ @model = owners.first && owners.first.class
13
+ @scope = nil
14
+ @owners_by_key = nil
15
+ end
16
+
17
+ def run
18
+ unless owners.first.association(reflection.name).loaded?
19
+ preload
20
+ end
21
+ end
22
+
23
+ def preload
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def scope
28
+ @scope ||= build_scope
29
+ end
30
+
31
+ def records_for(ids)
32
+ scope.where(association_key.in(ids))
33
+ end
34
+
35
+ def table
36
+ klass.arel_table
37
+ end
38
+
39
+ # The name of the key on the associated records
40
+ def association_key_name
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # This is overridden by HABTM as the condition should be on the foreign_key column in
45
+ # the join table
46
+ def association_key
47
+ table[association_key_name]
48
+ end
49
+
50
+ # The name of the key on the model which declares the association
51
+ def owner_key_name
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # We're converting to a string here because postgres will return the aliased association
56
+ # key in a habtm as a string (for whatever reason)
57
+ def owners_by_key
58
+ @owners_by_key ||= owners.group_by do |owner|
59
+ key = owner[owner_key_name]
60
+ key && key.to_s
61
+ end
62
+ end
63
+
64
+ def options
65
+ reflection.options
66
+ end
67
+
68
+ private
69
+
70
+ def associated_records_by_owner
71
+ owners_map = owners_by_key
72
+ owner_keys = owners_map.keys.compact
73
+
74
+ if klass.nil? || owner_keys.empty?
75
+ records = []
76
+ else
77
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
78
+ # Make several smaller queries if necessary or make one query if the adapter supports it
79
+ sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
80
+ records = sliced.map { |slice| records_for(slice).to_a }.flatten
81
+ end
82
+
83
+ # Each record may have multiple owners, and vice-versa
84
+ records_by_owner = Hash[owners.map { |owner| [owner, []] }]
85
+ records.each do |record|
86
+ owner_key = record[association_key_name].to_s
87
+
88
+ owners_map[owner_key].each do |owner|
89
+ records_by_owner[owner] << record
90
+ end
91
+ end
92
+ records_by_owner
93
+ end
94
+
95
+ def reflection_scope
96
+ @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
97
+ end
98
+
99
+ def build_scope
100
+ scope = klass.unscoped
101
+ scope.default_scoped = true
102
+
103
+ values = reflection_scope.values
104
+ preload_values = preload_scope.values
105
+
106
+ scope.where_values = Array(values[:where]) + Array(preload_values[:where])
107
+ scope.references_values = Array(values[:references]) + Array(preload_values[:references])
108
+
109
+ scope.select! preload_values[:select] || values[:select] || table[Arel.star]
110
+ scope.includes! preload_values[:includes] || values[:includes]
111
+
112
+ if options[:as]
113
+ scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
114
+ end
115
+
116
+ scope
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class BelongsTo < SingularAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.options[:primary_key] || klass && klass.primary_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.foreign_key
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class CollectionAssociation < Association #:nodoc:
5
+
6
+ private
7
+
8
+ def build_scope
9
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
10
+ end
11
+
12
+ def preload
13
+ associated_records_by_owner.each do |owner, records|
14
+ association = owner.association(reflection.name)
15
+ association.loaded!
16
+ association.target.concat(records)
17
+ records.each { |record| association.set_inverse_instance(record) }
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasAndBelongsToMany < CollectionAssociation #:nodoc:
5
+ attr_reader :join_table
6
+
7
+ def initialize(klass, records, reflection, preload_options)
8
+ super
9
+ @join_table = Arel::Table.new(reflection.join_table).alias('t0')
10
+ end
11
+
12
+ # Unlike the other associations, we want to get a raw array of rows so that we can
13
+ # access the aliased column on the join table
14
+ def records_for(ids)
15
+ scope = super
16
+ klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
17
+ end
18
+
19
+ def owner_key_name
20
+ reflection.active_record_primary_key
21
+ end
22
+
23
+ def association_key_name
24
+ 'ar_association_key_name'
25
+ end
26
+
27
+ def association_key
28
+ join_table[reflection.foreign_key]
29
+ end
30
+
31
+ private
32
+
33
+ # Once we have used the join table column (in super), we manually instantiate the
34
+ # actual records, ensuring that we don't create more than one instances of the same
35
+ # record
36
+ def associated_records_by_owner
37
+ records = {}
38
+ super.each_value do |rows|
39
+ rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
40
+ end
41
+ end
42
+
43
+ def build_scope
44
+ super.joins(join).select(join_select)
45
+ end
46
+
47
+ def join_select
48
+ association_key.as(Arel.sql(association_key_name))
49
+ end
50
+
51
+ def join
52
+ condition = table[reflection.association_primary_key].eq(
53
+ join_table[reflection.association_foreign_key])
54
+
55
+ table.create_join(join_table, table.create_on(condition))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasMany < CollectionAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.foreign_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.active_record_primary_key
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasManyThrough < CollectionAssociation #:nodoc:
5
+ include ThroughAssociation
6
+
7
+ def associated_records_by_owner
8
+ records_by_owner = super
9
+
10
+ if reflection_scope.distinct_value
11
+ records_by_owner.each_value { |records| records.uniq! }
12
+ end
13
+
14
+ records_by_owner
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasOne < SingularAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.foreign_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.active_record_primary_key
12
+ end
13
+
14
+ private
15
+
16
+ def build_scope
17
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasOneThrough < SingularAssociation #:nodoc:
5
+ include ThroughAssociation
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class SingularAssociation < Association #:nodoc:
5
+
6
+ private
7
+
8
+ def preload
9
+ associated_records_by_owner.each do |owner, associated_records|
10
+ record = associated_records.first
11
+
12
+ association = owner.association(reflection.name)
13
+ association.target = record
14
+ association.set_inverse_instance(record)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ module ThroughAssociation #:nodoc:
5
+
6
+ def through_reflection
7
+ reflection.through_reflection
8
+ end
9
+
10
+ def source_reflection
11
+ reflection.source_reflection
12
+ end
13
+
14
+ def associated_records_by_owner
15
+ through_records = through_records_by_owner
16
+
17
+ Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
18
+
19
+ through_records.each do |owner, records|
20
+ records.map! { |r| r.send(source_reflection.name) }.flatten!
21
+ records.compact!
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def through_records_by_owner
28
+ Preloader.new(owners, through_reflection.name, through_scope).run
29
+
30
+ Hash[owners.map do |owner|
31
+ through_records = Array.wrap(owner.send(through_reflection.name))
32
+
33
+ # Dont cache the association - we would only be caching a subset
34
+ if (through_scope != through_reflection.klass.unscoped) ||
35
+ (reflection.options[:source_type] && through_reflection.collection?)
36
+ owner.association(through_reflection.name).reset
37
+ end
38
+
39
+ [owner, through_records]
40
+ end]
41
+ end
42
+
43
+ def through_scope
44
+ through_scope = through_reflection.klass.unscoped
45
+
46
+ if options[:source_type]
47
+ through_scope.where! reflection.foreign_type => options[:source_type]
48
+ else
49
+ unless reflection_scope.where_values.empty?
50
+ through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
51
+ through_scope.where_values = reflection_scope.values[:where]
52
+ end
53
+
54
+ through_scope.references! reflection_scope.values[:references]
55
+ through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
56
+ end
57
+
58
+ through_scope
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end