activerecord 4.2.0 → 5.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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1537 -789
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +16 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +23 -9
  11. data/lib/active_record/associations/association_scope.rb +74 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +26 -29
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +12 -20
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +61 -33
  21. data/lib/active_record/associations/collection_proxy.rb +81 -35
  22. data/lib/active_record/associations/foreign_association.rb +11 -0
  23. data/lib/active_record/associations/has_many_association.rb +21 -57
  24. data/lib/active_record/associations/has_many_through_association.rb +15 -45
  25. data/lib/active_record/associations/has_one_association.rb +13 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +37 -21
  28. data/lib/active_record/associations/preloader/association.rb +51 -53
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +18 -8
  34. data/lib/active_record/associations/singular_association.rb +8 -8
  35. data/lib/active_record/associations/through_association.rb +22 -9
  36. data/lib/active_record/associations.rb +321 -212
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +79 -15
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +51 -81
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +37 -15
  52. data/lib/active_record/attribute_set.rb +34 -3
  53. data/lib/active_record/attributes.rb +199 -73
  54. data/lib/active_record/autosave_association.rb +73 -25
  55. data/lib/active_record/base.rb +35 -27
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  101. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  102. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +38 -15
  121. data/lib/active_record/core.rb +109 -114
  122. data/lib/active_record/counter_cache.rb +14 -25
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +115 -79
  125. data/lib/active_record/errors.rb +88 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +2 -2
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +84 -46
  130. data/lib/active_record/gem_version.rb +2 -2
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +27 -25
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration/command_recorder.rb +59 -18
  140. data/lib/active_record/migration/compatibility.rb +126 -0
  141. data/lib/active_record/migration.rb +372 -114
  142. data/lib/active_record/model_schema.rb +128 -38
  143. data/lib/active_record/nested_attributes.rb +71 -32
  144. data/lib/active_record/no_touching.rb +1 -1
  145. data/lib/active_record/null_relation.rb +16 -8
  146. data/lib/active_record/persistence.rb +124 -80
  147. data/lib/active_record/query_cache.rb +15 -18
  148. data/lib/active_record/querying.rb +10 -9
  149. data/lib/active_record/railtie.rb +28 -19
  150. data/lib/active_record/railties/controller_runtime.rb +1 -1
  151. data/lib/active_record/railties/databases.rake +67 -51
  152. data/lib/active_record/readonly_attributes.rb +1 -1
  153. data/lib/active_record/reflection.rb +318 -139
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  155. data/lib/active_record/relation/batches.rb +139 -34
  156. data/lib/active_record/relation/calculations.rb +80 -102
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +167 -97
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +38 -41
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  167. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  169. data/lib/active_record/relation/predicate_builder.rb +124 -82
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +323 -257
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +11 -10
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/relation.rb +176 -115
  177. data/lib/active_record/result.rb +4 -3
  178. data/lib/active_record/runtime_registry.rb +1 -1
  179. data/lib/active_record/sanitization.rb +95 -66
  180. data/lib/active_record/schema.rb +26 -22
  181. data/lib/active_record/schema_dumper.rb +62 -38
  182. data/lib/active_record/schema_migration.rb +11 -17
  183. data/lib/active_record/scoping/default.rb +24 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/scoping.rb +32 -15
  186. data/lib/active_record/secure_token.rb +38 -0
  187. data/lib/active_record/serialization.rb +2 -4
  188. data/lib/active_record/statement_cache.rb +16 -14
  189. data/lib/active_record/store.rb +8 -3
  190. data/lib/active_record/suppressor.rb +58 -0
  191. data/lib/active_record/table_metadata.rb +68 -0
  192. data/lib/active_record/tasks/database_tasks.rb +59 -42
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  196. data/lib/active_record/timestamp.rb +20 -9
  197. data/lib/active_record/touch_later.rb +58 -0
  198. data/lib/active_record/transactions.rb +159 -67
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -41
  201. data/lib/active_record/type/date_time.rb +2 -38
  202. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  203. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  204. data/lib/active_record/type/internal/timezone.rb +15 -0
  205. data/lib/active_record/type/serialized.rb +21 -14
  206. data/lib/active_record/type/time.rb +10 -16
  207. data/lib/active_record/type/type_map.rb +4 -4
  208. data/lib/active_record/type.rb +66 -17
  209. data/lib/active_record/type_caster/connection.rb +29 -0
  210. data/lib/active_record/type_caster/map.rb +19 -0
  211. data/lib/active_record/type_caster.rb +7 -0
  212. data/lib/active_record/validations/absence.rb +23 -0
  213. data/lib/active_record/validations/associated.rb +10 -3
  214. data/lib/active_record/validations/length.rb +24 -0
  215. data/lib/active_record/validations/presence.rb +11 -12
  216. data/lib/active_record/validations/uniqueness.rb +29 -18
  217. data/lib/active_record/validations.rb +33 -32
  218. data/lib/active_record.rb +9 -2
  219. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  220. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
  221. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
  222. data/lib/rails/generators/active_record/migration.rb +7 -0
  223. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  224. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  225. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  226. metadata +60 -34
  227. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  228. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  229. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  231. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  232. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  233. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  234. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  235. data/lib/active_record/type/big_integer.rb +0 -13
  236. data/lib/active_record/type/binary.rb +0 -50
  237. data/lib/active_record/type/boolean.rb +0 -30
  238. data/lib/active_record/type/decimal.rb +0 -40
  239. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  240. data/lib/active_record/type/decorator.rb +0 -14
  241. data/lib/active_record/type/float.rb +0 -19
  242. data/lib/active_record/type/integer.rb +0 -55
  243. data/lib/active_record/type/mutable.rb +0 -16
  244. data/lib/active_record/type/numeric.rb +0 -36
  245. data/lib/active_record/type/string.rb +0 -36
  246. data/lib/active_record/type/text.rb +0 -11
  247. data/lib/active_record/type/time_value.rb +0 -38
  248. data/lib/active_record/type/unsigned_integer.rb +0 -15
  249. data/lib/active_record/type/value.rb +0 -101
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/string/filters'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Has Many Through Association
5
3
  module Associations
@@ -13,21 +11,6 @@ module ActiveRecord
13
11
  @through_association = nil
14
12
  end
15
13
 
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.
21
- def size
22
- if has_cached_counter?
23
- owner._read_attribute cached_counter_attribute_name(reflection)
24
- elsif loaded?
25
- target.size
26
- else
27
- super
28
- end
29
- end
30
-
31
14
  def concat(*records)
32
15
  unless owner.new_record?
33
16
  records.flatten.each do |record|
@@ -55,25 +38,14 @@ module ActiveRecord
55
38
  def insert_record(record, validate = true, raise = false)
56
39
  ensure_not_nested
57
40
 
58
- if record.new_record?
59
- if raise
60
- record.save!(:validate => validate)
61
- else
62
- return unless record.save(:validate => validate)
63
- end
41
+ if raise
42
+ record.save!(:validate => validate)
43
+ else
44
+ return unless record.save(:validate => validate)
64
45
  end
65
46
 
66
47
  save_through_record(record)
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
48
 
75
- update_counter_in_database(1)
76
- end
77
49
  record
78
50
  end
79
51
 
@@ -94,6 +66,11 @@ module ActiveRecord
94
66
 
95
67
  through_record = through_association.build(*options_for_through_record)
96
68
  through_record.send("#{source_reflection.name}=", record)
69
+
70
+ if options[:source_type]
71
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
72
+ end
73
+
97
74
  through_record
98
75
  end
99
76
  end
@@ -138,7 +115,7 @@ module ActiveRecord
138
115
  def update_through_counter?(method)
139
116
  case method
140
117
  when :destroy
141
- !inverse_updates_counter_cache?(through_reflection)
118
+ !through_reflection.inverse_updates_counter_cache?
142
119
  when :nullify
143
120
  false
144
121
  else
@@ -161,17 +138,15 @@ module ActiveRecord
161
138
  if scope.klass.primary_key
162
139
  count = scope.destroy_all.length
163
140
  else
164
- scope.each do |record|
165
- record._run_destroy_callbacks
166
- end
141
+ scope.each(&:_run_destroy_callbacks)
167
142
 
168
143
  arel = scope.arel
169
144
 
170
- stmt = Arel::DeleteManager.new arel.engine
145
+ stmt = Arel::DeleteManager.new
171
146
  stmt.from scope.klass.arel_table
172
147
  stmt.wheres = arel.constraints
173
148
 
174
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
149
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
175
150
  end
176
151
  when :nullify
177
152
  count = scope.update_all(source_reflection.foreign_key => nil)
@@ -188,9 +163,9 @@ module ActiveRecord
188
163
 
189
164
  if through_reflection.collection? && update_through_counter?(method)
190
165
  update_counter(-count, through_reflection)
166
+ else
167
+ update_counter(-count)
191
168
  end
192
-
193
- update_counter(-count)
194
169
  end
195
170
 
196
171
  def through_records_for(record)
@@ -228,11 +203,6 @@ module ActiveRecord
228
203
  def invertible_for?(record)
229
204
  false
230
205
  end
231
-
232
- def through_reflection_updates_counter_cache?
233
- counter_name = cached_counter_attribute_name
234
- inverse_updates_counter_named?(counter_name, through_reflection)
235
- end
236
206
  end
237
207
  end
238
208
  end
@@ -1,7 +1,8 @@
1
1
  module ActiveRecord
2
- # = Active Record Belongs To Has One Association
2
+ # = Active Record Has One Association
3
3
  module Associations
4
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
5
6
 
6
7
  def handle_dependency
7
8
  case options[:dependent]
@@ -10,9 +11,16 @@ module ActiveRecord
10
11
 
11
12
  when :restrict_with_error
12
13
  if load_target
13
- record = klass.human_attribute_name(reflection.name).downcase
14
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
15
- false
14
+ record = owner.class.human_attribute_name(reflection.name).downcase
15
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil
16
+ if message
17
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
18
+ The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1.
19
+ Please use `:'restrict_dependent_destroy.has_one'` instead.
20
+ MESSAGE
21
+ end
22
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record)
23
+ throw(:abort)
16
24
  end
17
25
 
18
26
  else
@@ -57,7 +65,7 @@ module ActiveRecord
57
65
  when :destroy
58
66
  target.destroy
59
67
  when :nullify
60
- target.update_columns(reflection.foreign_key => nil)
68
+ target.update_columns(reflection.foreign_key => nil) if target.persisted?
61
69
  end
62
70
  end
63
71
  end
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
 
26
26
  def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
27
27
  joins = []
28
- bind_values = []
28
+ binds = []
29
29
  tables = tables.reverse
30
30
 
31
31
  scope_chain_index = 0
@@ -43,23 +43,36 @@ module ActiveRecord
43
43
 
44
44
  constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
45
45
 
46
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
46
47
  scope_chain_items = scope_chain[scope_chain_index].map do |item|
47
48
  if item.is_a?(Relation)
48
49
  item
49
50
  else
50
- ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
51
+ ActiveRecord::Relation.create(klass, table, predicate_builder)
52
+ .instance_exec(node, &item)
51
53
  end
52
54
  end
53
55
  scope_chain_index += 1
54
56
 
55
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
57
+ klass_scope =
58
+ if klass.current_scope
59
+ klass.current_scope.clone
60
+ else
61
+ relation = ActiveRecord::Relation.create(
62
+ klass,
63
+ table,
64
+ predicate_builder,
65
+ )
66
+ klass.send(:build_default_scope, relation)
67
+ end
68
+ scope_chain_items.concat [klass_scope].compact
56
69
 
57
70
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
58
71
  left.merge right
59
72
  end
60
73
 
61
74
  if rel && !rel.arel.constraints.empty?
62
- bind_values.concat rel.bind_values
75
+ binds += rel.bound_attributes
63
76
  constraint = constraint.and rel.arel.constraints
64
77
  end
65
78
 
@@ -67,9 +80,8 @@ module ActiveRecord
67
80
  value = foreign_klass.base_class.name
68
81
  column = klass.columns_hash[reflection.type.to_s]
69
82
 
70
- substitute = klass.connection.substitute_at(column)
71
- bind_values.push [column, value]
72
- constraint = constraint.and table[reflection.type].eq substitute
83
+ binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
84
+ constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
73
85
  end
74
86
 
75
87
  joins << table.create_join(table, table.create_on(constraint), join_type)
@@ -78,7 +90,7 @@ module ActiveRecord
78
90
  foreign_table, foreign_klass = table, klass
79
91
  end
80
92
 
81
- JoinInformation.new joins, bind_values
93
+ JoinInformation.new joins, binds
82
94
  end
83
95
 
84
96
  # Builds equality condition.
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  def columns
23
- @tables.flat_map { |t| t.column_aliases }
23
+ @tables.flat_map(&:column_aliases)
24
24
  end
25
25
 
26
26
  # An array of [column_name, alias] pairs for the table
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  @alias_cache[node][column]
33
33
  end
34
34
 
35
- class Table < Struct.new(:node, :columns)
35
+ class Table < Struct.new(:node, :columns) # :nodoc:
36
36
  def table
37
37
  Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
38
38
  end
@@ -93,8 +93,7 @@ module ActiveRecord
93
93
  # joins # => []
94
94
  #
95
95
  def initialize(base, associations, joins)
96
- @alias_tracker = AliasTracker.create(base.connection, joins)
97
- @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
96
+ @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
98
97
  tree = self.class.make_tree associations
99
98
  @join_root = JoinBase.new base, build(tree, base)
100
99
  @join_root.children.each { |child| construct_tables! @join_root, child }
@@ -104,9 +103,14 @@ module ActiveRecord
104
103
  join_root.drop(1).map!(&:reflection)
105
104
  end
106
105
 
107
- def join_constraints(outer_joins)
106
+ def join_constraints(outer_joins, join_type)
108
107
  joins = join_root.children.flat_map { |child|
109
- make_inner_joins join_root, child
108
+
109
+ if join_type == Arel::Nodes::OuterJoin
110
+ make_left_outer_joins join_root, child
111
+ else
112
+ make_inner_joins join_root, child
113
+ end
110
114
  }
111
115
 
112
116
  joins.concat outer_joins.flat_map { |oj|
@@ -132,9 +136,9 @@ module ActiveRecord
132
136
  def instantiate(result_set, aliases)
133
137
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
138
 
135
- seen = Hash.new { |h,parent_klass|
136
- h[parent_klass] = Hash.new { |i,parent_id|
137
- i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
139
+ seen = Hash.new { |i, object_id|
140
+ i[object_id] = Hash.new { |j, child_class|
141
+ j[child_class] = {}
138
142
  }
139
143
  }
140
144
 
@@ -151,7 +155,8 @@ module ActiveRecord
151
155
 
152
156
  message_bus.instrument('instantiation.active_record', payload) do
153
157
  result_set.each { |row_hash|
154
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
158
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
159
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
155
160
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
156
161
  }
157
162
  end
@@ -176,6 +181,14 @@ module ActiveRecord
176
181
  [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
177
182
  end
178
183
 
184
+ def make_left_outer_joins(parent, child)
185
+ tables = child.tables
186
+ join_type = Arel::Nodes::OuterJoin
187
+ info = make_constraints parent, child, tables, join_type
188
+
189
+ [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
190
+ end
191
+
179
192
  def make_inner_joins(parent, child)
180
193
  tables = child.tables
181
194
  join_type = Arel::Nodes::InnerJoin
@@ -215,7 +228,7 @@ module ActiveRecord
215
228
 
216
229
  def find_reflection(klass, name)
217
230
  klass._reflect_on_association(name) or
218
- raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
231
+ raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
219
232
  end
220
233
 
221
234
  def build(associations, base_klass)
@@ -233,31 +246,34 @@ module ActiveRecord
233
246
  end
234
247
 
235
248
  def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
236
- primary_id = ar_parent.id
249
+ return if ar_parent.nil?
237
250
 
238
251
  parent.children.each do |node|
239
252
  if node.reflection.collection?
240
253
  other = ar_parent.association(node.reflection.name)
241
254
  other.loaded!
242
- else
243
- if ar_parent.association_cache.key?(node.reflection.name)
244
- model = ar_parent.association(node.reflection.name).target
245
- construct(model, node, row, rs, seen, model_cache, aliases)
246
- next
247
- end
255
+ elsif ar_parent.association_cached?(node.reflection.name)
256
+ model = ar_parent.association(node.reflection.name).target
257
+ construct(model, node, row, rs, seen, model_cache, aliases)
258
+ next
248
259
  end
249
260
 
250
261
  key = aliases.column_alias(node, node.primary_key)
251
262
  id = row[key]
252
- next if id.nil?
263
+ if id.nil?
264
+ nil_association = ar_parent.association(node.reflection.name)
265
+ nil_association.loaded!
266
+ next
267
+ end
253
268
 
254
- model = seen[parent.base_klass][primary_id][node.base_klass][id]
269
+ model = seen[ar_parent.object_id][node.base_klass][id]
255
270
 
256
271
  if model
257
272
  construct(model, node, row, rs, seen, model_cache, aliases)
258
273
  else
259
274
  model = construct_model(ar_parent, node, row, model_cache, id, aliases)
260
- seen[parent.base_klass][primary_id][node.base_klass][id] = model
275
+ model.readonly!
276
+ seen[ar_parent.object_id][node.base_klass][id] = model
261
277
  construct(model, node, row, rs, seen, model_cache, aliases)
262
278
  end
263
279
  end
@@ -12,7 +12,6 @@ module ActiveRecord
12
12
  @preload_scope = preload_scope
13
13
  @model = owners.first && owners.first.class
14
14
  @scope = nil
15
- @owners_by_key = nil
16
15
  @preloaded_records = []
17
16
  end
18
17
 
@@ -33,7 +32,7 @@ module ActiveRecord
33
32
  end
34
33
 
35
34
  def query_scope(ids)
36
- scope.where(association_key.in(ids))
35
+ scope.where(association_key_name => ids)
37
36
  end
38
37
 
39
38
  def table
@@ -48,7 +47,7 @@ module ActiveRecord
48
47
  # This is overridden by HABTM as the condition should be on the foreign_key column in
49
48
  # the join table
50
49
  def association_key
51
- table[association_key_name]
50
+ klass.arel_attribute(association_key_name, table)
52
51
  end
53
52
 
54
53
  # The name of the key on the model which declares the association
@@ -56,18 +55,6 @@ module ActiveRecord
56
55
  raise NotImplementedError
57
56
  end
58
57
 
59
- def owners_by_key
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
69
- end
70
-
71
58
  def options
72
59
  reflection.options
73
60
  end
@@ -75,32 +62,33 @@ module ActiveRecord
75
62
  private
76
63
 
77
64
  def associated_records_by_owner(preloader)
78
- owners_map = owners_by_key
79
- owner_keys = owners_map.keys.compact
80
-
81
- # Each record may have multiple owners, and vice-versa
82
- records_by_owner = owners.each_with_object({}) do |owner,h|
83
- h[owner] = []
65
+ records = load_records
66
+ owners.each_with_object({}) do |owner, result|
67
+ result[owner] = records[convert_key(owner[owner_key_name])] || []
84
68
  end
69
+ end
85
70
 
86
- if owner_keys.any?
87
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
88
- # Make several smaller queries if necessary or make one query if the adapter supports it
89
- sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
90
-
91
- records = load_slices sliced
92
- records.each do |record, owner_key|
93
- owners_map[owner_key].each do |owner|
94
- records_by_owner[owner] << record
95
- end
71
+ def owner_keys
72
+ unless defined?(@owner_keys)
73
+ @owner_keys = owners.map do |owner|
74
+ owner[owner_key_name]
96
75
  end
76
+ @owner_keys.uniq!
77
+ @owner_keys.compact!
97
78
  end
98
-
99
- records_by_owner
79
+ @owner_keys
100
80
  end
101
81
 
102
82
  def key_conversion_required?
103
- association_key_type != owner_key_type
83
+ @key_conversion_required ||= association_key_type != owner_key_type
84
+ end
85
+
86
+ def convert_key(key)
87
+ if key_conversion_required?
88
+ key.to_s
89
+ else
90
+ key
91
+ end
104
92
  end
105
93
 
106
94
  def association_key_type
@@ -111,17 +99,17 @@ module ActiveRecord
111
99
  @model.type_for_attribute(owner_key_name.to_s).type
112
100
  end
113
101
 
114
- def load_slices(slices)
115
- @preloaded_records = slices.flat_map { |slice|
102
+ def load_records
103
+ return {} if owner_keys.empty?
104
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
105
+ # Make several smaller queries if necessary or make one query if the adapter supports it
106
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
107
+ @preloaded_records = slices.flat_map do |slice|
116
108
  records_for(slice)
117
- }
118
-
119
- @preloaded_records.map { |record|
120
- key = record[association_key_name]
121
- key = key.to_s if key_conversion_required?
122
-
123
- [record, key]
124
- }
109
+ end
110
+ @preloaded_records.group_by do |record|
111
+ convert_key(record[association_key_name])
112
+ end
125
113
  end
126
114
 
127
115
  def reflection_scope
@@ -131,19 +119,29 @@ module ActiveRecord
131
119
  def build_scope
132
120
  scope = klass.unscoped
133
121
 
134
- values = reflection_scope.values
135
- reflection_binds = reflection_scope.bind_values
122
+ values = reflection_scope.values
136
123
  preload_values = preload_scope.values
137
- preload_binds = preload_scope.bind_values
138
124
 
139
- scope.where_values = Array(values[:where]) + Array(preload_values[:where])
125
+ scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
140
126
  scope.references_values = Array(values[:references]) + Array(preload_values[:references])
141
- scope.bind_values = (reflection_binds + preload_binds)
142
127
 
143
- scope._select! preload_values[:select] || values[:select] || table[Arel.star]
128
+ if preload_values[:select] || values[:select]
129
+ scope._select!(preload_values[:select] || values[:select])
130
+ end
144
131
  scope.includes! preload_values[:includes] || values[:includes]
145
- scope.joins! preload_values[:joins] || values[:joins]
146
- scope.order! preload_values[:order] || values[:order]
132
+ if preload_scope.joins_values.any?
133
+ scope.joins!(preload_scope.joins_values)
134
+ else
135
+ scope.joins!(reflection_scope.joins_values)
136
+ end
137
+
138
+ if order_values = preload_values[:order] || values[:order]
139
+ scope.order!(order_values)
140
+ end
141
+
142
+ if preload_values[:reordering] || values[:reordering]
143
+ scope.reordering_value = true
144
+ end
147
145
 
148
146
  if preload_values[:readonly] || values[:readonly]
149
147
  scope.readonly!
@@ -153,7 +151,7 @@ module ActiveRecord
153
151
  scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
154
152
  end
155
153
 
156
- scope.unscope_values = Array(values[:unscope])
154
+ scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
157
155
  klass.default_scoped.merge(scope)
158
156
  end
159
157
  end
@@ -2,13 +2,8 @@ module ActiveRecord
2
2
  module Associations
3
3
  class Preloader
4
4
  class CollectionAssociation < Association #:nodoc:
5
-
6
5
  private
7
6
 
8
- def build_scope
9
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
10
- end
11
-
12
7
  def preload(preloader)
13
8
  associated_records_by_owner(preloader).each do |owner, records|
14
9
  association = owner.association(reflection.name)
@@ -17,7 +12,6 @@ module ActiveRecord
17
12
  records.each { |record| association.set_inverse_instance(record) }
18
13
  end
19
14
  end
20
-
21
15
  end
22
16
  end
23
17
  end
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  records_by_owner = super
9
9
 
10
10
  if reflection_scope.distinct_value
11
- records_by_owner.each_value { |records| records.uniq! }
11
+ records_by_owner.each_value(&:uniq!)
12
12
  end
13
13
 
14
14
  records_by_owner
@@ -2,7 +2,6 @@ module ActiveRecord
2
2
  module Associations
3
3
  class Preloader
4
4
  class HasOne < SingularAssociation #:nodoc:
5
-
6
5
  def association_key_name
7
6
  reflection.foreign_key
8
7
  end
@@ -10,13 +9,6 @@ module ActiveRecord
10
9
  def owner_key_name
11
10
  reflection.active_record_primary_key
12
11
  end
13
-
14
- private
15
-
16
- def build_scope
17
- super.order(preload_scope.values[:order] || reflection_scope.values[:order])
18
- end
19
-
20
12
  end
21
13
  end
22
14
  end
@@ -18,7 +18,8 @@ module ActiveRecord
18
18
  through_records = owners.map do |owner|
19
19
  association = owner.association through_reflection.name
20
20
 
21
- [owner, Array(association.reader)]
21
+ center = target_records_from_association(association)
22
+ [owner, Array(center)]
22
23
  end
23
24
 
24
25
  reset_association owners, through_reflection.name
@@ -37,28 +38,35 @@ module ActiveRecord
37
38
  }
38
39
  end
39
40
 
40
- record_offset = {}
41
- @preloaded_records.each_with_index do |record,i|
42
- record_offset[record] = i
43
- end
44
-
45
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
41
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
46
42
  pl_to_middle = center.group_by { |record| middle_to_pl[record] }
47
43
 
48
44
  records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
49
45
  rhs_records = middles.flat_map { |r|
50
46
  association = r.association source_reflection.name
51
47
 
52
- association.reader
48
+ target_records_from_association(association)
53
49
  }.compact
54
50
 
55
- rhs_records.sort_by { |rhs| record_offset[rhs] }
51
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
52
+ if reflection_scope.values[:order].present?
53
+ @id_map ||= id_to_index_map @preloaded_records
54
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
55
+ else
56
+ rhs_records
57
+ end
56
58
  end
57
- }
59
+ end
58
60
  end
59
61
 
60
62
  private
61
63
 
64
+ def id_to_index_map(ids)
65
+ id_map = {}
66
+ ids.each_with_index { |id, index| id_map[id] = index }
67
+ id_map
68
+ end
69
+
62
70
  def reset_association(owners, association_name)
63
71
  should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
72
  (reflection.options[:source_type] && through_reflection.collection?)
@@ -78,18 +86,23 @@ module ActiveRecord
78
86
  if options[:source_type]
79
87
  scope.where! reflection.foreign_type => options[:source_type]
80
88
  else
81
- unless reflection_scope.where_values.empty?
89
+ unless reflection_scope.where_clause.empty?
82
90
  scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
83
- scope.where_values = reflection_scope.values[:where]
84
- scope.bind_values = reflection_scope.bind_values
91
+ scope.where_clause = reflection_scope.where_clause
85
92
  end
86
93
 
87
94
  scope.references! reflection_scope.values[:references]
88
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
95
+ if scope.eager_loading? && order_values = reflection_scope.values[:order]
96
+ scope = scope.order(order_values)
97
+ end
89
98
  end
90
99
 
91
100
  scope
92
101
  end
102
+
103
+ def target_records_from_association(association)
104
+ association.loaded? ? association.target : association.reader
105
+ end
93
106
  end
94
107
  end
95
108
  end