activerecord 4.1.0 → 4.2.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -21,8 +21,11 @@ module ActiveRecord
21
21
  super && reflection == other.reflection
22
22
  end
23
23
 
24
+ JoinInformation = Struct.new :joins, :binds
25
+
24
26
  def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
25
27
  joins = []
28
+ bind_values = []
26
29
  tables = tables.reverse
27
30
 
28
31
  scope_chain_index = 0
@@ -34,14 +37,9 @@ module ActiveRecord
34
37
  table = tables.shift
35
38
  klass = reflection.klass
36
39
 
37
- case reflection.source_macro
38
- when :belongs_to
39
- key = reflection.association_primary_key
40
- foreign_key = reflection.foreign_key
41
- else
42
- key = reflection.foreign_key
43
- foreign_key = reflection.active_record_primary_key
44
- end
40
+ join_keys = reflection.join_keys(klass)
41
+ key = join_keys.key
42
+ foreign_key = join_keys.foreign_key
45
43
 
46
44
  constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
47
45
 
@@ -54,27 +52,33 @@ module ActiveRecord
54
52
  end
55
53
  scope_chain_index += 1
56
54
 
57
- scope_chain_items.concat [klass.send(:build_default_scope)].compact
55
+ scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
58
56
 
59
57
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
60
58
  left.merge right
61
59
  end
62
60
 
63
- if reflection.type
64
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
65
- end
66
-
67
61
  if rel && !rel.arel.constraints.empty?
62
+ bind_values.concat rel.bind_values
68
63
  constraint = constraint.and rel.arel.constraints
69
64
  end
70
65
 
66
+ if reflection.type
67
+ value = foreign_klass.base_class.name
68
+ column = klass.columns_hash[reflection.type.to_s]
69
+
70
+ substitute = klass.connection.substitute_at(column)
71
+ bind_values.push [column, value]
72
+ constraint = constraint.and table[reflection.type].eq substitute
73
+ end
74
+
71
75
  joins << table.create_join(table, table.create_on(constraint), join_type)
72
76
 
73
77
  # The current table in this iteration becomes the foreign table in the next
74
78
  foreign_table, foreign_klass = table, klass
75
79
  end
76
80
 
77
- joins
81
+ JoinInformation.new joins, bind_values
78
82
  end
79
83
 
80
84
  # Builds equality condition.
@@ -86,7 +90,7 @@ module ActiveRecord
86
90
  # end
87
91
  #
88
92
  # If I execute `Physician.joins(:appointments).to_a` then
89
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
93
+ # klass # => Physician
90
94
  # table # => #<Arel::Table @name="appointments" ...>
91
95
  # key # => physician_id
92
96
  # foreign_table # => #<Arel::Table @name="physicians" ...>
@@ -19,7 +19,6 @@ module ActiveRecord
19
19
 
20
20
  def initialize(base_klass, children)
21
21
  @base_klass = base_klass
22
- @column_names_with_alias = nil
23
22
  @children = children
24
23
  end
25
24
 
@@ -94,7 +94,7 @@ module ActiveRecord
94
94
  #
95
95
  def initialize(base, associations, joins)
96
96
  @alias_tracker = AliasTracker.create(base.connection, joins)
97
- @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
97
+ @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
98
98
  tree = self.class.make_tree associations
99
99
  @join_root = JoinBase.new base, build(tree, base)
100
100
  @join_root.children.each { |child| construct_tables! @join_root, child }
@@ -131,7 +131,6 @@ module ActiveRecord
131
131
 
132
132
  def instantiate(result_set, aliases)
133
133
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
- type_caster = result_set.column_type primary_key
135
134
 
136
135
  seen = Hash.new { |h,parent_klass|
137
136
  h[parent_klass] = Hash.new { |i,parent_id|
@@ -143,12 +142,20 @@ module ActiveRecord
143
142
  parents = model_cache[join_root]
144
143
  column_aliases = aliases.column_aliases join_root
145
144
 
146
- result_set.each { |row_hash|
147
- primary_id = type_caster.type_cast row_hash[primary_key]
148
- parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
149
- construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
145
+ message_bus = ActiveSupport::Notifications.instrumenter
146
+
147
+ payload = {
148
+ record_count: result_set.length,
149
+ class_name: join_root.base_klass.name
150
150
  }
151
151
 
152
+ message_bus.instrument('instantiation.active_record', payload) do
153
+ result_set.each { |row_hash|
154
+ parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
155
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
156
+ }
157
+ end
158
+
152
159
  parents.values
153
160
  end
154
161
 
@@ -164,17 +171,17 @@ module ActiveRecord
164
171
  def make_outer_joins(parent, child)
165
172
  tables = table_aliases_for(parent, child)
166
173
  join_type = Arel::Nodes::OuterJoin
167
- joins = make_constraints parent, child, tables, join_type
174
+ info = make_constraints parent, child, tables, join_type
168
175
 
169
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
176
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
170
177
  end
171
178
 
172
179
  def make_inner_joins(parent, child)
173
180
  tables = child.tables
174
181
  join_type = Arel::Nodes::InnerJoin
175
- joins = make_constraints parent, child, tables, join_type
182
+ info = make_constraints parent, child, tables, join_type
176
183
 
177
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
184
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
178
185
  end
179
186
 
180
187
  def table_aliases_for(parent, node)
@@ -207,7 +214,7 @@ module ActiveRecord
207
214
  end
208
215
 
209
216
  def find_reflection(klass, name)
210
- klass.reflect_on_association(name) or
217
+ klass._reflect_on_association(name) or
211
218
  raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
212
219
  end
213
220
 
@@ -215,8 +222,9 @@ module ActiveRecord
215
222
  associations.map do |name, right|
216
223
  reflection = find_reflection base_klass, name
217
224
  reflection.check_validity!
225
+ reflection.check_eager_loadable!
218
226
 
219
- if reflection.options[:polymorphic]
227
+ if reflection.polymorphic?
220
228
  raise EagerLoadPolymorphicError.new(reflection)
221
229
  end
222
230
 
@@ -57,9 +57,15 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def owners_by_key
60
- @owners_by_key ||= owners.group_by do |owner|
61
- owner[owner_key_name]
62
- end
60
+ @owners_by_key ||= if key_conversion_required?
61
+ owners.group_by do |owner|
62
+ owner[owner_key_name].to_s
63
+ end
64
+ else
65
+ owners.group_by do |owner|
66
+ owner[owner_key_name]
67
+ end
68
+ end
63
69
  end
64
70
 
65
71
  def options
@@ -93,13 +99,28 @@ module ActiveRecord
93
99
  records_by_owner
94
100
  end
95
101
 
102
+ def key_conversion_required?
103
+ association_key_type != owner_key_type
104
+ end
105
+
106
+ def association_key_type
107
+ @klass.type_for_attribute(association_key_name.to_s).type
108
+ end
109
+
110
+ def owner_key_type
111
+ @model.type_for_attribute(owner_key_name.to_s).type
112
+ end
113
+
96
114
  def load_slices(slices)
97
115
  @preloaded_records = slices.flat_map { |slice|
98
116
  records_for(slice)
99
117
  }
100
118
 
101
119
  @preloaded_records.map { |record|
102
- [record, record[association_key_name]]
120
+ key = record[association_key_name]
121
+ key = key.to_s if key_conversion_required?
122
+
123
+ [record, key]
103
124
  }
104
125
  end
105
126
 
@@ -111,26 +132,28 @@ module ActiveRecord
111
132
  scope = klass.unscoped
112
133
 
113
134
  values = reflection_scope.values
135
+ reflection_binds = reflection_scope.bind_values
114
136
  preload_values = preload_scope.values
137
+ preload_binds = preload_scope.bind_values
115
138
 
116
139
  scope.where_values = Array(values[:where]) + Array(preload_values[:where])
117
140
  scope.references_values = Array(values[:references]) + Array(preload_values[:references])
141
+ scope.bind_values = (reflection_binds + preload_binds)
118
142
 
119
- scope.select! preload_values[:select] || values[:select] || table[Arel.star]
143
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
120
144
  scope.includes! preload_values[:includes] || values[:includes]
145
+ scope.joins! preload_values[:joins] || values[:joins]
146
+ scope.order! preload_values[:order] || values[:order]
121
147
 
122
- if preload_values.key? :order
123
- scope.order! preload_values[:order]
124
- else
125
- if values.key? :order
126
- scope.order! values[:order]
127
- end
148
+ if preload_values[:readonly] || values[:readonly]
149
+ scope.readonly!
128
150
  end
129
151
 
130
152
  if options[:as]
131
153
  scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
132
154
  end
133
155
 
156
+ scope.unscope_values = Array(values[:unscope])
134
157
  klass.default_scoped.merge(scope)
135
158
  end
136
159
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  reset_association owners, through_reflection.name
25
25
 
26
- middle_records = through_records.map { |(_,rec)| rec }.flatten
26
+ middle_records = through_records.flat_map { |(_,rec)| rec }
27
27
 
28
28
  preloaders = preloader.preload(middle_records,
29
29
  source_reflection.name,
@@ -63,7 +63,7 @@ module ActiveRecord
63
63
  should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
64
  (reflection.options[:source_type] && through_reflection.collection?)
65
65
 
66
- # Dont cache the association - we would only be caching a subset
66
+ # Don't cache the association - we would only be caching a subset
67
67
  if should_reset
68
68
  owners.each { |owner|
69
69
  owner.association(association_name).reset
@@ -81,10 +81,11 @@ module ActiveRecord
81
81
  unless reflection_scope.where_values.empty?
82
82
  scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
83
83
  scope.where_values = reflection_scope.values[:where]
84
+ scope.bind_values = reflection_scope.bind_values
84
85
  end
85
86
 
86
87
  scope.references! reflection_scope.values[:references]
87
- scope.order! reflection_scope.values[:order] if scope.eager_loading?
88
+ scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
88
89
  end
89
90
 
90
91
  scope
@@ -2,33 +2,42 @@ module ActiveRecord
2
2
  module Associations
3
3
  # Implements the details of eager loading of Active Record associations.
4
4
  #
5
- # Note that 'eager loading' and 'preloading' are actually the same thing.
6
- # However, there are two different eager loading strategies.
5
+ # Suppose that you have the following two Active Record models:
7
6
  #
8
- # The first one is by using table joins. This was only strategy available
9
- # prior to Rails 2.1. Suppose that you have an Author model with columns
10
- # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
11
- # this strategy, Active Record would try to retrieve all data for an author
12
- # and all of its books via a single query:
7
+ # class Author < ActiveRecord::Base
8
+ # # columns: name, age
9
+ # has_many :books
10
+ # end
13
11
  #
14
- # SELECT * FROM authors
15
- # LEFT OUTER JOIN books ON authors.id = books.author_id
16
- # WHERE authors.name = 'Ken Akamatsu'
12
+ # class Book < ActiveRecord::Base
13
+ # # columns: title, sales
14
+ # end
17
15
  #
18
- # However, this could result in many rows that contain redundant data. After
19
- # having received the first row, we already have enough data to instantiate
20
- # the Author object. In all subsequent rows, only the data for the joined
21
- # 'books' table is useful; the joined 'authors' data is just redundant, and
22
- # processing this redundant data takes memory and CPU time. The problem
23
- # quickly becomes worse and worse as the level of eager loading increases
24
- # (i.e. if Active Record is to eager load the associations' associations as
25
- # well).
16
+ # When you load an author with all associated books Active Record will make
17
+ # multiple queries like this:
18
+ #
19
+ # Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
20
+ #
21
+ # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
22
+ # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
23
+ #
24
+ # Active Record saves the ids of the records from the first query to use in
25
+ # the second. Depending on the number of associations involved there can be
26
+ # arbitrarily many SQL queries made.
27
+ #
28
+ # However, if there is a WHERE clause that spans across tables Active
29
+ # Record will fall back to a slightly more resource-intensive single query:
30
+ #
31
+ # Author.includes(:books).where(books: {title: 'Illiad'}).to_a
32
+ # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
33
+ # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
34
+ # FROM `authors`
35
+ # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
36
+ # WHERE `books`.`title` = 'Illiad'
37
+ #
38
+ # This could result in many rows that contain redundant data and it performs poorly at scale
39
+ # and is therefore only used when necessary.
26
40
  #
27
- # The second strategy is to use multiple database queries, one for each
28
- # level of association. Since Rails 2.1, this is the default strategy. In
29
- # situations where a table join is necessary (e.g. when the +:conditions+
30
- # option references an association's column), it will fallback to the table
31
- # join strategy.
32
41
  class Preloader #:nodoc:
33
42
  extend ActiveSupport::Autoload
34
43
 
@@ -80,7 +89,7 @@ module ActiveRecord
80
89
  # { author: :avatar }
81
90
  # [ :books, { author: :avatar } ]
82
91
 
83
- NULL_RELATION = Struct.new(:values).new({})
92
+ NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
84
93
 
85
94
  def preload(records, associations, preload_scope = nil)
86
95
  records = Array.wrap(records).compact.uniq
@@ -112,13 +121,14 @@ module ActiveRecord
112
121
  end
113
122
 
114
123
  def preloaders_for_hash(association, records, scope)
115
- parent, child = association.to_a.first # hash should only be of length 1
116
-
117
- loaders = preloaders_for_one parent, records, scope
124
+ association.flat_map { |parent, child|
125
+ loaders = preloaders_for_one parent, records, scope
118
126
 
119
- recs = loaders.flat_map(&:preloaded_records).uniq
120
- loaders.concat Array.wrap(child).flat_map { |assoc|
121
- preloaders_on assoc, recs, scope
127
+ recs = loaders.flat_map(&:preloaded_records).uniq
128
+ loaders.concat Array.wrap(child).flat_map { |assoc|
129
+ preloaders_on assoc, recs, scope
130
+ }
131
+ loaders
122
132
  }
123
133
  end
124
134
 
@@ -140,36 +150,14 @@ module ActiveRecord
140
150
  end
141
151
 
142
152
  def grouped_records(association, records)
143
- reflection_records = records_by_reflection(association, records)
144
-
145
- reflection_records.each_with_object({}) do |(reflection, r_records),h|
146
- h[reflection] = r_records.group_by { |record|
147
- association_klass(reflection, record)
148
- }
149
- end
150
- end
151
-
152
- def records_by_reflection(association, records)
153
- records.group_by do |record|
154
- reflection = record.class.reflect_on_association(association)
155
-
156
- reflection || raise_config_error(record, association)
157
- end
158
- end
159
-
160
- def raise_config_error(record, association)
161
- raise ActiveRecord::ConfigurationError,
162
- "Association named '#{association}' was not found on #{record.class.name}; " \
163
- "perhaps you misspelled it?"
164
- end
165
-
166
- def association_klass(reflection, record)
167
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
168
- klass = record.read_attribute(reflection.foreign_type.to_s)
169
- klass && klass.constantize
170
- else
171
- reflection.klass
153
+ h = {}
154
+ records.each do |record|
155
+ next unless record
156
+ assoc = record.association(association)
157
+ klasses = h[assoc.reflection] ||= {}
158
+ (klasses[assoc.klass] ||= []) << record
172
159
  end
160
+ h
173
161
  end
174
162
 
175
163
  class AlreadyLoaded
@@ -190,6 +178,7 @@ module ActiveRecord
190
178
  class NullPreloader
191
179
  def self.new(klass, owners, reflection, preload_scope); self; end
192
180
  def self.run(preloader); end
181
+ def self.preloaded_records; []; end
193
182
  end
194
183
 
195
184
  def preloader_for(reflection, owners, rhs_klass)
@@ -198,6 +187,7 @@ module ActiveRecord
198
187
  if owners.first.association(reflection.name).loaded?
199
188
  return AlreadyLoaded
200
189
  end
190
+ reflection.check_preloadable!
201
191
 
202
192
  case reflection.macro
203
193
  when :has_many
@@ -18,11 +18,11 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def create(attributes = {}, &block)
21
- create_record(attributes, &block)
21
+ _create_record(attributes, &block)
22
22
  end
23
23
 
24
24
  def create!(attributes = {}, &block)
25
- create_record(attributes, true, &block)
25
+ _create_record(attributes, true, &block)
26
26
  end
27
27
 
28
28
  def build(attributes = {})
@@ -38,8 +38,29 @@ module ActiveRecord
38
38
  scope.scope_for_create.stringify_keys.except(klass.primary_key)
39
39
  end
40
40
 
41
+ def get_records
42
+ if reflection.scope_chain.any?(&:any?) ||
43
+ scope.eager_loading? ||
44
+ klass.current_scope ||
45
+ klass.default_scopes.any?
46
+
47
+ return scope.limit(1).to_a
48
+ end
49
+
50
+ conn = klass.connection
51
+ sc = reflection.association_scope_cache(conn, owner) do
52
+ StatementCache.create(conn) { |params|
53
+ as = AssociationScope.create { params.bind }
54
+ target_scope.merge(as.scope(self, conn)).limit(1)
55
+ }
56
+ end
57
+
58
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
59
+ sc.execute binds, klass, klass.connection
60
+ end
61
+
41
62
  def find_target
42
- if record = scope.take
63
+ if record = get_records.first
43
64
  set_inverse_instance record
44
65
  end
45
66
  end
@@ -52,7 +73,7 @@ module ActiveRecord
52
73
  replace(record)
53
74
  end
54
75
 
55
- def create_record(attributes, raise_error = false)
76
+ def _create_record(attributes, raise_error = false)
56
77
  record = build_record(attributes)
57
78
  yield(record) if block_given?
58
79
  saved = record.save
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module Associations
4
4
  module ThroughAssociation #:nodoc:
5
5
 
6
- delegate :source_reflection, :through_reflection, :chain, :to => :reflection
6
+ delegate :source_reflection, :through_reflection, :to => :reflection
7
7
 
8
8
  protected
9
9
 
@@ -13,10 +13,16 @@ module ActiveRecord
13
13
  # 2. To get the type conditions for any STI models in the chain
14
14
  def target_scope
15
15
  scope = super
16
- chain.drop(1).each do |reflection|
16
+ reflection.chain.drop(1).each do |reflection|
17
+ relation = reflection.klass.all
18
+
19
+ reflection_scope = reflection.scope
20
+ if reflection_scope && reflection_scope.arity.zero?
21
+ relation.merge!(reflection_scope)
22
+ end
23
+
17
24
  scope.merge!(
18
- reflection.klass.all.
19
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
25
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
20
26
  )
21
27
  end
22
28
  scope
@@ -39,12 +45,16 @@ module ActiveRecord
39
45
  def construct_join_attributes(*records)
40
46
  ensure_mutable
41
47
 
42
- join_attributes = {
43
- source_reflection.foreign_key =>
44
- records.map { |record|
45
- record.send(source_reflection.association_primary_key(reflection.klass))
46
- }
47
- }
48
+ if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
49
+ join_attributes = { source_reflection.name => records }
50
+ else
51
+ join_attributes = {
52
+ source_reflection.foreign_key =>
53
+ records.map { |record|
54
+ record.send(source_reflection.association_primary_key(reflection.klass))
55
+ }
56
+ }
57
+ end
48
58
 
49
59
  if options[:source_type]
50
60
  join_attributes[source_reflection.foreign_type] =
@@ -61,18 +71,17 @@ module ActiveRecord
61
71
  # Note: this does not capture all cases, for example it would be crazy to try to
62
72
  # properly support stale-checking for nested associations.
63
73
  def stale_state
64
- if through_reflection.macro == :belongs_to
74
+ if through_reflection.belongs_to?
65
75
  owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
66
76
  end
67
77
  end
68
78
 
69
79
  def foreign_key_present?
70
- through_reflection.macro == :belongs_to &&
71
- !owner[through_reflection.foreign_key].nil?
80
+ through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
72
81
  end
73
82
 
74
83
  def ensure_mutable
75
- if source_reflection.macro != :belongs_to
84
+ unless source_reflection.belongs_to?
76
85
  raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
77
86
  end
78
87
  end