activerecord 4.1.16 → 4.2.11.3

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 +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  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/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/string/filters'
1
2
 
2
3
  module ActiveRecord
3
4
  # = Active Record Has Many Through Association
@@ -12,13 +13,14 @@ module ActiveRecord
12
13
  @through_association = nil
13
14
  end
14
15
 
15
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
16
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
17
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
18
- # SELECT query if you use #length.
16
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
17
+ # if the collection hasn't been loaded, and by calling collection.size if
18
+ # it has. If the collection will likely have a size greater than zero,
19
+ # and if fetching the collection will be needed afterwards, one less
20
+ # SELECT query will be generated by using #length instead.
19
21
  def size
20
22
  if has_cached_counter?
21
- owner.send(:read_attribute, cached_counter_attribute_name)
23
+ owner._read_attribute cached_counter_attribute_name(reflection)
22
24
  elsif loaded?
23
25
  target.size
24
26
  else
@@ -62,7 +64,16 @@ module ActiveRecord
62
64
  end
63
65
 
64
66
  save_through_record(record)
65
- update_counter(1)
67
+ if has_cached_counter? && !through_reflection_updates_counter_cache?
68
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
69
+ Automatic updating of counter caches on through associations has been
70
+ deprecated, and will be removed in Rails 5. Instead, please set the
71
+ appropriate `counter_cache` options on the `has_many` and `belongs_to`
72
+ for your associations to #{through_reflection.name}.
73
+ MSG
74
+
75
+ update_counter_in_database(1)
76
+ end
66
77
  record
67
78
  end
68
79
 
@@ -72,19 +83,22 @@ module ActiveRecord
72
83
  @through_association ||= owner.association(through_reflection.name)
73
84
  end
74
85
 
75
- # We temporarily cache through record that has been build, because if we build a
76
- # through record in build_record and then subsequently call insert_record, then we
77
- # want to use the exact same object.
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
78
88
  #
79
- # However, after insert_record has been called, we clear the cache entry because
80
- # we want it to be possible to have multiple instances of the same record in an
81
- # association
89
+ # However, after insert_record has been called, the cache is cleared in
90
+ # order to allow multiple instances of the same record in an association.
82
91
  def build_through_record(record)
83
92
  @through_records[record.object_id] ||= begin
84
93
  ensure_mutable
85
94
 
86
95
  through_record = through_association.build(*options_for_through_record)
87
96
  through_record.send("#{source_reflection.name}=", record)
97
+
98
+ if options[:source_type]
99
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
+ end
101
+
88
102
  through_record
89
103
  end
90
104
  end
@@ -112,9 +126,9 @@ module ActiveRecord
112
126
 
113
127
  inverse = source_reflection.inverse_of
114
128
  if inverse
115
- if inverse.macro == :has_many
129
+ if inverse.collection?
116
130
  record.send(inverse.name) << build_through_record(record)
117
- elsif inverse.macro == :has_one
131
+ elsif inverse.has_one?
118
132
  record.send("#{inverse.name}=", build_through_record(record))
119
133
  end
120
134
  end
@@ -123,7 +137,7 @@ module ActiveRecord
123
137
  end
124
138
 
125
139
  def target_reflection_has_associated_record?
126
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
140
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
127
141
  end
128
142
 
129
143
  def update_through_counter?(method)
@@ -137,13 +151,13 @@ module ActiveRecord
137
151
  end
138
152
  end
139
153
 
154
+ def delete_or_nullify_all_records(method)
155
+ delete_records(load_target, method)
156
+ end
157
+
140
158
  def delete_records(records, method)
141
159
  ensure_not_nested
142
160
 
143
- # This is unoptimised; it will load all the target records
144
- # even when we just want to delete everything.
145
- records = load_target if records == :all
146
-
147
161
  scope = through_association.scope
148
162
  scope.where! construct_join_attributes(*records)
149
163
 
@@ -152,8 +166,8 @@ module ActiveRecord
152
166
  if scope.klass.primary_key
153
167
  count = scope.destroy_all.length
154
168
  else
155
- scope.to_a.each do |record|
156
- record.run_callbacks :destroy
169
+ scope.each do |record|
170
+ record._run_destroy_callbacks
157
171
  end
158
172
 
159
173
  arel = scope.arel
@@ -177,11 +191,11 @@ module ActiveRecord
177
191
  klass.decrement_counter counter, records.map(&:id)
178
192
  end
179
193
 
180
- if through_reflection.macro == :has_many && update_through_counter?(method)
194
+ if through_reflection.collection? && update_through_counter?(method)
181
195
  update_counter(-count, through_reflection)
196
+ else
197
+ update_counter(-count)
182
198
  end
183
-
184
- update_counter(-count)
185
199
  end
186
200
 
187
201
  def through_records_for(record)
@@ -198,7 +212,7 @@ module ActiveRecord
198
212
  records.each do |record|
199
213
  through_records = through_records_for(record)
200
214
 
201
- if through_reflection.macro == :has_many
215
+ if through_reflection.collection?
202
216
  through_records.each { |r| through_association.target.delete(r) }
203
217
  else
204
218
  if through_records.include?(through_association.target)
@@ -212,13 +226,22 @@ module ActiveRecord
212
226
 
213
227
  def find_target
214
228
  return [] unless target_reflection_has_associated_record?
215
- scope.to_a
229
+ get_records
216
230
  end
217
231
 
218
232
  # NOTE - not sure that we can actually cope with inverses here
219
233
  def invertible_for?(record)
220
234
  false
221
235
  end
236
+
237
+ def has_cached_counter?(reflection = reflection())
238
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
239
+ end
240
+
241
+ def through_reflection_updates_counter_cache?
242
+ counter_name = cached_counter_attribute_name
243
+ inverse_updates_counter_named?(counter_name, through_reflection)
244
+ end
222
245
  end
223
246
  end
224
247
  end
@@ -1,8 +1,8 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Belongs To Has One Association
4
3
  module Associations
5
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
6
6
 
7
7
  def handle_dependency
8
8
  case options[:dependent]
@@ -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,39 @@ module ActiveRecord
54
52
  end
55
53
  scope_chain_index += 1
56
54
 
57
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
55
+ klass_scope =
56
+ if klass.current_scope && klass.current_scope.values.blank?
57
+ klass.unscoped
58
+ else
59
+ klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))
60
+ end
61
+ scope_chain_items.concat [klass_scope].compact
58
62
 
59
63
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
60
64
  left.merge right
61
65
  end
62
66
 
63
- if reflection.type
64
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
65
- end
66
-
67
67
  if rel && !rel.arel.constraints.empty?
68
+ bind_values.concat rel.bind_values
68
69
  constraint = constraint.and rel.arel.constraints
69
70
  end
70
71
 
72
+ if reflection.type
73
+ value = foreign_klass.base_class.name
74
+ column = klass.columns_hash[reflection.type.to_s]
75
+
76
+ substitute = klass.connection.substitute_at(column)
77
+ bind_values.push [column, value]
78
+ constraint = constraint.and table[reflection.type].eq substitute
79
+ end
80
+
71
81
  joins << table.create_join(table, table.create_on(constraint), join_type)
72
82
 
73
83
  # The current table in this iteration becomes the foreign table in the next
74
84
  foreign_table, foreign_klass = table, klass
75
85
  end
76
86
 
77
- joins
87
+ JoinInformation.new joins, bind_values
78
88
  end
79
89
 
80
90
  # Builds equality condition.
@@ -86,7 +96,7 @@ module ActiveRecord
86
96
  # end
87
97
  #
88
98
  # If I execute `Physician.joins(:appointments).to_a` then
89
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
99
+ # klass # => Physician
90
100
  # table # => #<Arel::Table @name="appointments" ...>
91
101
  # key # => physician_id
92
102
  # 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,13 +142,21 @@ 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
- parent_key = primary_key ? row_hash[primary_key] : row_hash
148
- primary_id = type_caster.type_cast parent_key
149
- parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
150
- 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
151
150
  }
152
151
 
152
+ message_bus.instrument('instantiation.active_record', payload) do
153
+ result_set.each { |row_hash|
154
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
155
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
156
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
157
+ }
158
+ end
159
+
153
160
  parents.values
154
161
  end
155
162
 
@@ -165,17 +172,17 @@ module ActiveRecord
165
172
  def make_outer_joins(parent, child)
166
173
  tables = table_aliases_for(parent, child)
167
174
  join_type = Arel::Nodes::OuterJoin
168
- joins = make_constraints parent, child, tables, join_type
175
+ info = make_constraints parent, child, tables, join_type
169
176
 
170
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
177
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
171
178
  end
172
179
 
173
180
  def make_inner_joins(parent, child)
174
181
  tables = child.tables
175
182
  join_type = Arel::Nodes::InnerJoin
176
- joins = make_constraints parent, child, tables, join_type
183
+ info = make_constraints parent, child, tables, join_type
177
184
 
178
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
185
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
179
186
  end
180
187
 
181
188
  def table_aliases_for(parent, node)
@@ -216,8 +223,9 @@ module ActiveRecord
216
223
  associations.map do |name, right|
217
224
  reflection = find_reflection base_klass, name
218
225
  reflection.check_validity!
226
+ reflection.check_eager_loadable!
219
227
 
220
- if reflection.options[:polymorphic]
228
+ if reflection.polymorphic?
221
229
  raise EagerLoadPolymorphicError.new(reflection)
222
230
  end
223
231
 
@@ -226,6 +234,7 @@ module ActiveRecord
226
234
  end
227
235
 
228
236
  def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
237
+ return if ar_parent.nil?
229
238
  primary_id = ar_parent.id
230
239
 
231
240
  parent.children.each do |node|
@@ -242,7 +251,11 @@ module ActiveRecord
242
251
 
243
252
  key = aliases.column_alias(node, node.primary_key)
244
253
  id = row[key]
245
- next if id.nil?
254
+ if id.nil?
255
+ nil_association = ar_parent.association(node.reflection.name)
256
+ nil_association.loaded!
257
+ next
258
+ end
246
259
 
247
260
  model = seen[parent.base_klass][primary_id][node.base_klass][id]
248
261
 
@@ -104,13 +104,11 @@ module ActiveRecord
104
104
  end
105
105
 
106
106
  def association_key_type
107
- column = @klass.column_types[association_key_name.to_s]
108
- column && column.type
107
+ @klass.type_for_attribute(association_key_name.to_s).type
109
108
  end
110
109
 
111
110
  def owner_key_type
112
- column = @model.column_types[owner_key_name.to_s]
113
- column && column.type
111
+ @model.type_for_attribute(owner_key_name.to_s).type
114
112
  end
115
113
 
116
114
  def load_slices(slices)
@@ -134,27 +132,32 @@ module ActiveRecord
134
132
  scope = klass.unscoped
135
133
 
136
134
  values = reflection_scope.values
135
+ reflection_binds = reflection_scope.bind_values
137
136
  preload_values = preload_scope.values
137
+ preload_binds = preload_scope.bind_values
138
138
 
139
139
  scope.where_values = Array(values[:where]) + Array(preload_values[:where])
140
140
  scope.references_values = Array(values[:references]) + Array(preload_values[:references])
141
+ scope.bind_values = (reflection_binds + preload_binds)
141
142
 
142
143
  scope._select! preload_values[:select] || values[:select] || table[Arel.star]
143
144
  scope.includes! preload_values[:includes] || values[:includes]
145
+ scope.joins! preload_values[:joins] || values[:joins]
146
+ scope.order! preload_values[:order] || values[:order]
144
147
 
145
- if preload_values.key? :order
146
- scope.order! preload_values[:order]
147
- else
148
- if values.key? :order
149
- scope.order! values[:order]
150
- end
148
+ if preload_values[:reordering] || values[:reordering]
149
+ scope.reordering_value = true
150
+ end
151
+
152
+ if preload_values[:readonly] || values[:readonly]
153
+ scope.readonly!
151
154
  end
152
155
 
153
156
  if options[:as]
154
157
  scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
155
158
  end
156
159
 
157
- scope.unscope_values = Array(values[:unscope])
160
+ scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
158
161
  klass.default_scoped.merge(scope)
159
162
  end
160
163
  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, author_id
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
@@ -151,7 +160,7 @@ module ActiveRecord
151
160
  h
152
161
  end
153
162
 
154
- class AlreadyLoaded
163
+ class AlreadyLoaded # :nodoc:
155
164
  attr_reader :owners, :reflection
156
165
 
157
166
  def initialize(klass, owners, reflection, preload_scope)
@@ -166,7 +175,7 @@ module ActiveRecord
166
175
  end
167
176
  end
168
177
 
169
- class NullPreloader
178
+ class NullPreloader # :nodoc:
170
179
  def self.new(klass, owners, reflection, preload_scope); self; end
171
180
  def self.run(preloader); end
172
181
  def self.preloaded_records; []; end
@@ -178,6 +187,7 @@ module ActiveRecord
178
187
  if owners.first.association(reflection.name).loaded?
179
188
  return AlreadyLoaded
180
189
  end
190
+ reflection.check_preloadable!
181
191
 
182
192
  case reflection.macro
183
193
  when :has_many
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  class SingularAssociation < Association #:nodoc:
4
4
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
5
5
  def reader(force_reload = false)
6
- if force_reload
6
+ if force_reload && klass
7
7
  klass.uncached { reload }
8
8
  elsif !loaded? || stale_target?
9
9
  reload
@@ -38,8 +38,23 @@ module ActiveRecord
38
38
  scope.scope_for_create.stringify_keys.except(klass.primary_key)
39
39
  end
40
40
 
41
+ def get_records
42
+ return scope.limit(1).to_a if skip_statement_cache?
43
+
44
+ conn = klass.connection
45
+ sc = reflection.association_scope_cache(conn, owner) do
46
+ StatementCache.create(conn) { |params|
47
+ as = AssociationScope.create { params.bind }
48
+ target_scope.merge(as.scope(self, conn)).limit(1)
49
+ }
50
+ end
51
+
52
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
53
+ sc.execute binds, klass, klass.connection
54
+ end
55
+
41
56
  def find_target
42
- if record = scope.take
57
+ if record = get_records.first
43
58
  set_inverse_instance record
44
59
  end
45
60
  end
@@ -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,14 +13,8 @@ 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
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
-
24
18
  scope.merge!(
25
19
  relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
26
20
  )
@@ -71,18 +65,17 @@ module ActiveRecord
71
65
  # Note: this does not capture all cases, for example it would be crazy to try to
72
66
  # properly support stale-checking for nested associations.
73
67
  def stale_state
74
- if through_reflection.macro == :belongs_to
68
+ if through_reflection.belongs_to?
75
69
  owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
76
70
  end
77
71
  end
78
72
 
79
73
  def foreign_key_present?
80
- through_reflection.macro == :belongs_to &&
81
- !owner[through_reflection.foreign_key].nil?
74
+ through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
82
75
  end
83
76
 
84
77
  def ensure_mutable
85
- if source_reflection.macro != :belongs_to
78
+ unless source_reflection.belongs_to?
86
79
  raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
87
80
  end
88
81
  end