activerecord 6.0.0 → 6.1.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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +872 -582
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -13
  6. data/lib/active_record/aggregations.rb +1 -2
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations.rb +116 -13
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +49 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +25 -8
  22. data/lib/active_record/associations/collection_proxy.rb +14 -7
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -3
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -9
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  38. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -12
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +12 -21
  45. data/lib/active_record/attributes.rb +32 -8
  46. data/lib/active_record/autosave_association.rb +63 -44
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +153 -24
  49. data/lib/active_record/coders/yaml_column.rb +1 -2
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +86 -37
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +4 -9
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -52
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -76
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  101. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  102. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  103. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql_adapter.rb +81 -57
  105. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  106. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  107. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  108. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  109. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  110. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  111. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  112. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  113. data/lib/active_record/connection_handling.rb +211 -81
  114. data/lib/active_record/core.rb +237 -69
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations.rb +124 -85
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  118. data/lib/active_record/database_configurations/database_config.rb +52 -9
  119. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  120. data/lib/active_record/database_configurations/url_config.rb +15 -41
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +40 -16
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +54 -11
  134. data/lib/active_record/gem_version.rb +1 -1
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +39 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +22 -17
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +27 -9
  143. data/lib/active_record/middleware/database_selector.rb +4 -2
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  146. data/lib/active_record/migration.rb +114 -84
  147. data/lib/active_record/migration/command_recorder.rb +53 -45
  148. data/lib/active_record/migration/compatibility.rb +70 -20
  149. data/lib/active_record/migration/join_table.rb +0 -1
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/databases.rake +267 -93
  159. data/lib/active_record/readonly_attributes.rb +4 -0
  160. data/lib/active_record/reflection.rb +77 -63
  161. data/lib/active_record/relation.rb +108 -67
  162. data/lib/active_record/relation/batches.rb +38 -32
  163. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  164. data/lib/active_record/relation/calculations.rb +102 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder.rb +55 -35
  170. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  171. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  173. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  174. data/lib/active_record/relation/query_methods.rb +340 -180
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +104 -58
  178. data/lib/active_record/result.rb +41 -34
  179. data/lib/active_record/runtime_registry.rb +2 -2
  180. data/lib/active_record/sanitization.rb +6 -17
  181. data/lib/active_record/schema_dumper.rb +34 -4
  182. data/lib/active_record/schema_migration.rb +2 -8
  183. data/lib/active_record/scoping.rb +0 -1
  184. data/lib/active_record/scoping/default.rb +0 -1
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/secure_token.rb +16 -8
  187. data/lib/active_record/serialization.rb +5 -3
  188. data/lib/active_record/signed_id.rb +116 -0
  189. data/lib/active_record/statement_cache.rb +20 -4
  190. data/lib/active_record/store.rb +3 -3
  191. data/lib/active_record/suppressor.rb +2 -2
  192. data/lib/active_record/table_metadata.rb +39 -36
  193. data/lib/active_record/tasks/database_tasks.rb +139 -113
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  197. data/lib/active_record/test_databases.rb +5 -4
  198. data/lib/active_record/test_fixtures.rb +38 -16
  199. data/lib/active_record/timestamp.rb +4 -7
  200. data/lib/active_record/touch_later.rb +20 -21
  201. data/lib/active_record/transactions.rb +22 -71
  202. data/lib/active_record/type.rb +8 -2
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type_caster/connection.rb +0 -1
  210. data/lib/active_record/type_caster/map.rb +8 -5
  211. data/lib/active_record/validations.rb +3 -3
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/arel.rb +15 -12
  216. data/lib/arel/attributes/attribute.rb +4 -0
  217. data/lib/arel/collectors/bind.rb +5 -0
  218. data/lib/arel/collectors/composite.rb +8 -0
  219. data/lib/arel/collectors/sql_string.rb +7 -0
  220. data/lib/arel/collectors/substitute_binds.rb +7 -0
  221. data/lib/arel/nodes.rb +3 -1
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/predications.rb +17 -24
  237. data/lib/arel/select_manager.rb +1 -2
  238. data/lib/arel/table.rb +13 -5
  239. data/lib/arel/visitors.rb +0 -7
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  247. data/lib/rails/generators/active_record/migration.rb +6 -2
  248. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  249. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  250. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  251. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  252. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  253. metadata +27 -24
  254. data/lib/active_record/attribute_decorators.rb +0 -90
  255. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  256. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  257. data/lib/active_record/define_callbacks.rb +0 -22
  258. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  259. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  260. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  261. data/lib/arel/attributes.rb +0 -22
  262. data/lib/arel/visitors/depth_first.rb +0 -204
  263. data/lib/arel/visitors/ibm_db.rb +0 -34
  264. data/lib/arel/visitors/informix.rb +0 -62
  265. data/lib/arel/visitors/mssql.rb +0 -157
  266. data/lib/arel/visitors/oracle.rb +0 -159
  267. data/lib/arel/visitors/oracle12.rb +0 -66
  268. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -5,7 +5,7 @@
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
6
  class SingularAssociation < Association #:nodoc:
7
7
  def self.valid_options(options)
8
- super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required]
8
+ super + [:required, :touch]
9
9
  end
10
10
 
11
11
  def self.define_accessors(model, reflection)
@@ -56,7 +56,7 @@ module ActiveRecord
56
56
  def ids_writer(ids)
57
57
  primary_key = reflection.association_primary_key
58
58
  pk_type = klass.type_for_attribute(primary_key)
59
- ids = Array(ids).reject(&:blank?)
59
+ ids = Array(ids).compact_blank
60
60
  ids.map! { |i| pk_type.cast(i) }
61
61
 
62
62
  records = klass.where(primary_key => ids).index_by do |r|
@@ -101,11 +101,11 @@ module ActiveRecord
101
101
  end
102
102
  end
103
103
 
104
- def build(attributes = {}, &block)
104
+ def build(attributes = nil, &block)
105
105
  if attributes.is_a?(Array)
106
106
  attributes.collect { |attr| build(attr, &block) }
107
107
  else
108
- add_to_target(build_record(attributes, &block))
108
+ add_to_target(build_record(attributes, &block), replace: true)
109
109
  end
110
110
  end
111
111
 
@@ -228,7 +228,7 @@ module ActiveRecord
228
228
  # If the collection has been loaded
229
229
  # it is equivalent to <tt>collection.size.zero?</tt>. If the
230
230
  # collection has not been loaded, it is equivalent to
231
- # <tt>collection.exists?</tt>. If the collection has not already been
231
+ # <tt>!collection.exists?</tt>. If the collection has not already been
232
232
  # loaded and you are going to fetch the records anyway it is better to
233
233
  # check <tt>collection.length.zero?</tt>.
234
234
  def empty?
@@ -278,13 +278,24 @@ module ActiveRecord
278
278
  target
279
279
  end
280
280
 
281
- def add_to_target(record, skip_callbacks = false, &block)
282
- if association_scope.distinct_value
281
+ def add_to_target(record, skip_callbacks: false, replace: false, &block)
282
+ if replace || association_scope.distinct_value
283
283
  index = @target.index(record)
284
284
  end
285
285
  replace_on_target(record, index, skip_callbacks, &block)
286
286
  end
287
287
 
288
+ def target=(record)
289
+ return super unless ActiveRecord::Base.has_many_inversing
290
+
291
+ case record
292
+ when Array
293
+ super
294
+ else
295
+ add_to_target(record, skip_callbacks: true, replace: true)
296
+ end
297
+ end
298
+
288
299
  def scope
289
300
  scope = super
290
301
  scope.none! if null_scope?
@@ -297,6 +308,8 @@ module ActiveRecord
297
308
 
298
309
  def find_from_target?
299
310
  loaded? ||
311
+ owner.strict_loading? ||
312
+ reflection.strict_loading? ||
300
313
  owner.new_record? ||
301
314
  target.any? { |record| record.new_record? || record.changed? }
302
315
  end
@@ -378,7 +391,9 @@ module ActiveRecord
378
391
  end
379
392
 
380
393
  def remove_records(existing_records, records, method)
381
- records.each { |record| callback(:before_remove, record) }
394
+ catch(:abort) do
395
+ records.each { |record| callback(:before_remove, record) }
396
+ end || return
382
397
 
383
398
  delete_records(existing_records, method) if existing_records.any?
384
399
  @target -= records
@@ -434,7 +449,9 @@ module ActiveRecord
434
449
  end
435
450
 
436
451
  def replace_on_target(record, index, skip_callbacks)
437
- callback(:before_add, record) unless skip_callbacks
452
+ catch(:abort) do
453
+ callback(:before_add, record)
454
+ end || return unless skip_callbacks
438
455
 
439
456
  set_inverse_instance(record)
440
457
 
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  # is computed directly through SQL and does not trigger by itself the
28
28
  # instantiation of the actual post records.
29
29
  class CollectionProxy < Relation
30
- def initialize(klass, association) #:nodoc:
30
+ def initialize(klass, association, **) #:nodoc:
31
31
  @association = association
32
32
  super klass
33
33
 
@@ -51,6 +51,7 @@ module ActiveRecord
51
51
  def loaded?
52
52
  @association.loaded?
53
53
  end
54
+ alias :loaded :loaded?
54
55
 
55
56
  ##
56
57
  # :method: select
@@ -100,7 +101,7 @@ module ActiveRecord
100
101
  # converting them into an array and iterating through them using
101
102
  # Array#select.
102
103
  #
103
- # person.pets.select { |pet| pet.name =~ /oo/ }
104
+ # person.pets.select { |pet| /oo/.match?(pet.name) }
104
105
  # # => [
105
106
  # # #<Pet id: 2, name: "Spook", person_id: 1>,
106
107
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
@@ -373,7 +374,7 @@ module ActiveRecord
373
374
  # person.pets
374
375
  # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
375
376
  #
376
- # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
377
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
377
378
  #
378
379
  # person.pets.replace(other_pets)
379
380
  #
@@ -920,7 +921,7 @@ module ActiveRecord
920
921
  !!@association.include?(record)
921
922
  end
922
923
 
923
- def proxy_association
924
+ def proxy_association # :nodoc:
924
925
  @association
925
926
  end
926
927
 
@@ -1086,22 +1087,28 @@ module ActiveRecord
1086
1087
  end
1087
1088
 
1088
1089
  def reset_scope # :nodoc:
1089
- @offsets = {}
1090
+ @offsets = @take = nil
1090
1091
  @scope = nil
1091
1092
  self
1092
1093
  end
1093
1094
 
1095
+ def inspect # :nodoc:
1096
+ load_target if find_from_target?
1097
+ super
1098
+ end
1099
+
1094
1100
  delegate_methods = [
1095
1101
  QueryMethods,
1096
1102
  SpawnMethods,
1097
1103
  ].flat_map { |klass|
1098
1104
  klass.public_instance_methods(false)
1099
- } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1105
+ } - self.public_instance_methods(false) - [:select] + [
1106
+ :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
1107
+ ]
1100
1108
 
1101
1109
  delegate(*delegate_methods, to: :scope)
1102
1110
 
1103
1111
  private
1104
-
1105
1112
  def find_nth_with_limit(index, limit)
1106
1113
  load_target if find_from_target?
1107
1114
  super
@@ -16,5 +16,18 @@ module ActiveRecord::Associations
16
16
  attrs[reflection.type] = nil if reflection.type.present?
17
17
  end
18
18
  end
19
+
20
+ private
21
+ # Sets the owner attributes on the given record
22
+ def set_owner_attributes(record)
23
+ return if options[:through]
24
+
25
+ key = owner._read_attribute(reflection.join_foreign_key)
26
+ record._write_attribute(reflection.join_primary_key, key)
27
+
28
+ if reflection.type
29
+ record._write_attribute(reflection.type, owner.class.polymorphic_name)
30
+ end
31
+ end
19
32
  end
20
33
  end
@@ -26,6 +26,28 @@ module ActiveRecord
26
26
  # No point in executing the counter update since we're going to destroy the parent anyway
27
27
  load_target.each { |t| t.destroyed_by_association = reflection }
28
28
  destroy_all
29
+ when :destroy_async
30
+ load_target.each do |t|
31
+ t.destroyed_by_association = reflection
32
+ end
33
+
34
+ unless target.empty?
35
+ association_class = target.first.class
36
+ primary_key_column = association_class.primary_key.to_sym
37
+
38
+ ids = target.collect do |assoc|
39
+ assoc.public_send(primary_key_column)
40
+ end
41
+
42
+ enqueue_destroy_association(
43
+ owner_model_name: owner.class.to_s,
44
+ owner_id: owner.id,
45
+ association_class: association_class.to_s,
46
+ association_ids: ids,
47
+ association_primary_key_column: primary_key_column,
48
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
49
+ )
50
+ end
29
51
  else
30
52
  delete_all
31
53
  end
@@ -37,7 +59,6 @@ module ActiveRecord
37
59
  end
38
60
 
39
61
  private
40
-
41
62
  # Returns the number of records in this collection.
42
63
  #
43
64
  # If the association has a counter cache it gets that value. Otherwise
@@ -53,7 +74,7 @@ module ActiveRecord
53
74
  # the loaded flag is set to true as well.
54
75
  def count_records
55
76
  count = if reflection.has_cached_counter?
56
- owner._read_attribute(reflection.counter_cache_column).to_i
77
+ owner.read_attribute(reflection.counter_cache_column).to_i
57
78
  else
58
79
  scope.count(:all)
59
80
  end
@@ -76,7 +97,7 @@ module ActiveRecord
76
97
  if reflection.counter_must_be_updated_by_has_many?
77
98
  counter = reflection.counter_cache_column
78
99
  owner.increment(counter, difference)
79
- owner.send(:clear_attribute_change, counter) # eww
100
+ owner.send(:"clear_#{counter}_change")
80
101
  end
81
102
  end
82
103
 
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
 
9
9
  def initialize(owner, reflection)
10
10
  super
11
- @through_records = {}
11
+ @through_records = {}.compare_by_identity
12
12
  end
13
13
 
14
14
  def concat(*records)
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # However, after insert_record has been called, the cache is cleared in
55
55
  # order to allow multiple instances of the same record in an association.
56
56
  def build_through_record(record)
57
- @through_records[record.object_id] ||= begin
57
+ @through_records[record] ||= begin
58
58
  ensure_mutable
59
59
 
60
60
  attributes = through_scope_attributes
@@ -65,7 +65,10 @@ module ActiveRecord
65
65
  end
66
66
  end
67
67
 
68
+ attr_reader :through_scope
69
+
68
70
  def through_scope_attributes
71
+ scope = through_scope || self.scope
69
72
  scope.where_values_hash(through_association.reflection.name.to_s).
70
73
  except!(through_association.reflection.foreign_key,
71
74
  through_association.reflection.klass.inheritance_column)
@@ -77,12 +80,13 @@ module ActiveRecord
77
80
  association.save!
78
81
  end
79
82
  ensure
80
- @through_records.delete(record.object_id)
83
+ @through_records.delete(record)
81
84
  end
82
85
 
83
86
  def build_record(attributes)
84
87
  ensure_not_nested
85
88
 
89
+ @through_scope = scope
86
90
  record = super
87
91
 
88
92
  inverse = source_reflection.inverse_of
@@ -95,6 +99,8 @@ module ActiveRecord
95
99
  end
96
100
 
97
101
  record
102
+ ensure
103
+ @through_scope = nil
98
104
  end
99
105
 
100
106
  def remove_records(existing_records, records, method)
@@ -202,7 +208,7 @@ module ActiveRecord
202
208
  end
203
209
  end
204
210
 
205
- @through_records.delete(record.object_id)
211
+ @through_records.delete(record)
206
212
  end
207
213
  end
208
214
 
@@ -32,6 +32,18 @@ module ActiveRecord
32
32
  target.destroyed_by_association = reflection
33
33
  target.destroy
34
34
  throw(:abort) unless target.destroyed?
35
+ when :destroy_async
36
+ primary_key_column = target.class.primary_key.to_sym
37
+ id = target.public_send(primary_key_column)
38
+
39
+ enqueue_destroy_association(
40
+ owner_model_name: owner.class.to_s,
41
+ owner_id: owner.id,
42
+ association_class: reflection.klass.to_s,
43
+ association_ids: [id],
44
+ association_primary_key_column: primary_key_column,
45
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
46
+ )
35
47
  when :nullify
36
48
  target.update_columns(nullified_owner_attributes) if target.persisted?
37
49
  end
@@ -81,7 +93,9 @@ module ActiveRecord
81
93
  target.delete
82
94
  when :destroy
83
95
  target.destroyed_by_association = reflection
84
- target.destroy
96
+ if target.persisted?
97
+ target.destroy
98
+ end
85
99
  else
86
100
  nullify_owner_attributes(target)
87
101
  remove_inverse_instance(target)
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  Table = Struct.new(:node, :columns) do # :nodoc:
35
35
  def column_aliases
36
36
  t = node.table
37
- columns.map { |column| t[column.name].as Arel.sql column.alias }
37
+ columns.map { |column| t[column.name].as(column.alias) }
38
38
  end
39
39
  end
40
40
  Column = Struct.new(:name, :alias)
@@ -70,18 +70,26 @@ module ActiveRecord
70
70
  @join_type = join_type
71
71
  end
72
72
 
73
+ def base_klass
74
+ join_root.base_klass
75
+ end
76
+
73
77
  def reflections
74
78
  join_root.drop(1).map!(&:reflection)
75
79
  end
76
80
 
77
- def join_constraints(joins_to_add, alias_tracker)
81
+ def join_constraints(joins_to_add, alias_tracker, references)
78
82
  @alias_tracker = alias_tracker
83
+ @joined_tables = {}
84
+ @references = {}
85
+
86
+ references.each do |table_name|
87
+ @references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
88
+ end unless references.empty?
79
89
 
80
- construct_tables!(join_root)
81
90
  joins = make_join_constraints(join_root, join_type)
82
91
 
83
92
  joins.concat joins_to_add.flat_map { |oj|
84
- construct_tables!(oj.join_root)
85
93
  if join_root.match? oj.join_root
86
94
  walk(join_root, oj.join_root, oj.join_type)
87
95
  else
@@ -90,18 +98,35 @@ module ActiveRecord
90
98
  }
91
99
  end
92
100
 
93
- def instantiate(result_set, &block)
101
+ def instantiate(result_set, strict_loading_value, &block)
94
102
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
95
103
 
96
- seen = Hash.new { |i, object_id|
97
- i[object_id] = Hash.new { |j, child_class|
104
+ seen = Hash.new { |i, parent|
105
+ i[parent] = Hash.new { |j, child_class|
98
106
  j[child_class] = {}
99
107
  }
100
- }
108
+ }.compare_by_identity
101
109
 
102
110
  model_cache = Hash.new { |h, klass| h[klass] = {} }
103
111
  parents = model_cache[join_root]
104
- column_aliases = aliases.column_aliases join_root
112
+
113
+ column_aliases = aliases.column_aliases(join_root)
114
+ column_names = []
115
+
116
+ result_set.columns.each do |name|
117
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
118
+ end
119
+
120
+ if column_names.empty?
121
+ column_types = {}
122
+ else
123
+ column_types = result_set.column_types
124
+ unless column_types.empty?
125
+ attribute_types = join_root.attribute_types
126
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
127
+ end
128
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
129
+ end
105
130
 
106
131
  message_bus = ActiveSupport::Notifications.instrumenter
107
132
 
@@ -113,8 +138,8 @@ module ActiveRecord
113
138
  message_bus.instrument("instantiation.active_record", payload) do
114
139
  result_set.each { |row_hash|
115
140
  parent_key = primary_key ? row_hash[primary_key] : row_hash
116
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
117
- construct(parent, join_root, row_hash, seen, model_cache)
141
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
142
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
118
143
  }
119
144
  end
120
145
 
@@ -122,30 +147,36 @@ module ActiveRecord
122
147
  end
123
148
 
124
149
  def apply_column_aliases(relation)
150
+ @join_root_alias = relation.select_values.empty?
125
151
  relation._select!(-> { aliases.columns })
126
152
  end
127
153
 
154
+ def each(&block)
155
+ join_root.each(&block)
156
+ end
157
+
128
158
  protected
129
159
  attr_reader :join_root, :join_type
130
160
 
131
161
  private
132
- attr_reader :alias_tracker
162
+ attr_reader :alias_tracker, :join_root_alias
133
163
 
134
164
  def aliases
135
165
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
- columns = join_part.column_names.each_with_index.map { |column_name, j|
166
+ column_names = if join_part == join_root && !join_root_alias
167
+ primary_key = join_root.primary_key
168
+ primary_key ? [primary_key] : []
169
+ else
170
+ join_part.column_names
171
+ end
172
+
173
+ columns = column_names.each_with_index.map { |column_name, j|
137
174
  Aliases::Column.new column_name, "t#{i}_r#{j}"
138
175
  }
139
176
  Aliases::Table.new(join_part, columns)
140
177
  }
141
178
  end
142
179
 
143
- def construct_tables!(join_root)
144
- join_root.each_children do |parent, child|
145
- child.tables = table_aliases_for(parent, child)
146
- end
147
- end
148
-
149
180
  def make_join_constraints(join_root, join_type)
150
181
  join_root.children.flat_map do |child|
151
182
  make_constraints(join_root, child, join_type)
@@ -155,23 +186,25 @@ module ActiveRecord
155
186
  def make_constraints(parent, child, join_type)
156
187
  foreign_table = parent.table
157
188
  foreign_klass = parent.base_klass
158
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
160
- end
189
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
190
+ table, terminated = @joined_tables[reflection]
191
+ root = reflection == child.reflection
161
192
 
162
- def table_aliases_for(parent, node)
163
- node.reflection.chain.map { |reflection|
164
- alias_tracker.aliased_table_for(
165
- reflection.table_name,
166
- table_alias_for(reflection, parent, reflection != node.reflection),
167
- reflection.klass.type_caster
168
- )
169
- }
170
- end
193
+ if table && (!root || !terminated)
194
+ @joined_tables[reflection] = [table, root] if root
195
+ next table, true
196
+ end
197
+
198
+ table_name = @references[reflection.name.to_sym]
199
+
200
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
201
+ name = reflection.alias_candidate(parent.table_name)
202
+ root ? name : "#{name}_join"
203
+ end
171
204
 
172
- def table_alias_for(reflection, parent, join)
173
- name = reflection.alias_candidate(parent.table_name)
174
- join ? "#{name}_join" : name
205
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
206
+ table
207
+ end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
175
208
  end
176
209
 
177
210
  def walk(left, right, join_type)
@@ -202,7 +235,7 @@ module ActiveRecord
202
235
  end
203
236
  end
204
237
 
205
- def construct(ar_parent, parent, row, seen, model_cache)
238
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
206
239
  return if ar_parent.nil?
207
240
 
208
241
  parent.children.each do |node|
@@ -211,7 +244,7 @@ module ActiveRecord
211
244
  other.loaded!
212
245
  elsif ar_parent.association_cached?(node.reflection.name)
213
246
  model = ar_parent.association(node.reflection.name).target
214
- construct(model, node, row, seen, model_cache)
247
+ construct(model, node, row, seen, model_cache, strict_loading_value)
215
248
  next
216
249
  end
217
250
 
@@ -223,24 +256,25 @@ module ActiveRecord
223
256
  next
224
257
  end
225
258
 
226
- model = seen[ar_parent.object_id][node][id]
259
+ model = seen[ar_parent][node][id]
227
260
 
228
261
  if model
229
- construct(model, node, row, seen, model_cache)
262
+ construct(model, node, row, seen, model_cache, strict_loading_value)
230
263
  else
231
- model = construct_model(ar_parent, node, row, model_cache, id)
264
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
232
265
 
233
- seen[ar_parent.object_id][node][id] = model
234
- construct(model, node, row, seen, model_cache)
266
+ seen[ar_parent][node][id] = model
267
+ construct(model, node, row, seen, model_cache, strict_loading_value)
235
268
  end
236
269
  end
237
270
  end
238
271
 
239
- def construct_model(record, node, row, model_cache, id)
272
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
240
273
  other = record.association(node.reflection.name)
241
274
 
242
275
  model = model_cache[node][id] ||=
243
276
  node.instantiate(row, aliases.column_aliases(node)) do |m|
277
+ m.strict_loading! if strict_loading_value
244
278
  other.set_inverse_instance(m)
245
279
  end
246
280
 
@@ -251,6 +285,7 @@ module ActiveRecord
251
285
  end
252
286
 
253
287
  model.readonly! if node.readonly?
288
+ model.strict_loading! if node.strict_loading?
254
289
  model
255
290
  end
256
291
  end