activerecord 5.2.4.2 → 6.0.2.2

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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +715 -566
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +9 -2
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/association_relation.rb +15 -6
  9. data/lib/active_record/associations.rb +20 -15
  10. data/lib/active_record/associations/association.rb +61 -20
  11. data/lib/active_record/associations/association_scope.rb +4 -6
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +12 -23
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency.rb +28 -28
  29. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +39 -31
  32. data/lib/active_record/associations/preloader/association.rb +38 -36
  33. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/attribute_assignment.rb +7 -10
  36. data/lib/active_record/attribute_methods.rb +28 -100
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  38. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  40. data/lib/active_record/attribute_methods/query.rb +2 -3
  41. data/lib/active_record/attribute_methods/read.rb +15 -53
  42. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  44. data/lib/active_record/attribute_methods/write.rb +17 -24
  45. data/lib/active_record/attributes.rb +13 -0
  46. data/lib/active_record/autosave_association.rb +2 -2
  47. data/lib/active_record/base.rb +2 -3
  48. data/lib/active_record/callbacks.rb +5 -19
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +104 -16
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +99 -123
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -8
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +187 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +138 -195
  61. data/lib/active_record/connection_adapters/column.rb +17 -13
  62. data/lib/active_record/connection_adapters/connection_specification.rb +53 -43
  63. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  66. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  67. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  68. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  69. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  70. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  71. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  81. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  82. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  83. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  87. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  88. data/lib/active_record/connection_adapters/postgresql_adapter.rb +164 -74
  89. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  90. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +129 -141
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +103 -59
  97. data/lib/active_record/counter_cache.rb +4 -29
  98. data/lib/active_record/database_configurations.rb +233 -0
  99. data/lib/active_record/database_configurations/database_config.rb +37 -0
  100. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  101. data/lib/active_record/database_configurations/url_config.rb +79 -0
  102. data/lib/active_record/dynamic_matchers.rb +1 -1
  103. data/lib/active_record/enum.rb +37 -7
  104. data/lib/active_record/errors.rb +15 -7
  105. data/lib/active_record/explain.rb +1 -1
  106. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  107. data/lib/active_record/fixture_set/render_context.rb +17 -0
  108. data/lib/active_record/fixture_set/table_row.rb +153 -0
  109. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  110. data/lib/active_record/fixtures.rb +145 -472
  111. data/lib/active_record/gem_version.rb +3 -3
  112. data/lib/active_record/inheritance.rb +13 -3
  113. data/lib/active_record/insert_all.rb +179 -0
  114. data/lib/active_record/integration.rb +68 -16
  115. data/lib/active_record/internal_metadata.rb +10 -2
  116. data/lib/active_record/locking/optimistic.rb +5 -6
  117. data/lib/active_record/locking/pessimistic.rb +3 -3
  118. data/lib/active_record/log_subscriber.rb +7 -26
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  121. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/migration/command_recorder.rb +50 -6
  124. data/lib/active_record/migration/compatibility.rb +76 -49
  125. data/lib/active_record/model_schema.rb +33 -9
  126. data/lib/active_record/nested_attributes.rb +2 -2
  127. data/lib/active_record/no_touching.rb +7 -0
  128. data/lib/active_record/persistence.rb +228 -24
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +32 -20
  131. data/lib/active_record/railtie.rb +80 -43
  132. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  133. data/lib/active_record/railties/controller_runtime.rb +30 -35
  134. data/lib/active_record/railties/databases.rake +199 -46
  135. data/lib/active_record/reflection.rb +32 -30
  136. data/lib/active_record/relation.rb +311 -80
  137. data/lib/active_record/relation/batches.rb +13 -10
  138. data/lib/active_record/relation/calculations.rb +53 -47
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +23 -27
  141. data/lib/active_record/relation/merger.rb +11 -20
  142. data/lib/active_record/relation/predicate_builder.rb +4 -6
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  145. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  146. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  147. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  148. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  149. data/lib/active_record/relation/query_attribute.rb +13 -8
  150. data/lib/active_record/relation/query_methods.rb +213 -64
  151. data/lib/active_record/relation/spawn_methods.rb +1 -1
  152. data/lib/active_record/relation/where_clause.rb +14 -10
  153. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  154. data/lib/active_record/result.rb +30 -11
  155. data/lib/active_record/sanitization.rb +32 -40
  156. data/lib/active_record/schema.rb +2 -11
  157. data/lib/active_record/schema_dumper.rb +22 -7
  158. data/lib/active_record/schema_migration.rb +5 -1
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/scoping/default.rb +4 -5
  161. data/lib/active_record/scoping/named.rb +20 -15
  162. data/lib/active_record/statement_cache.rb +30 -3
  163. data/lib/active_record/store.rb +87 -8
  164. data/lib/active_record/table_metadata.rb +10 -17
  165. data/lib/active_record/tasks/database_tasks.rb +194 -25
  166. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  167. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  168. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  169. data/lib/active_record/test_databases.rb +23 -0
  170. data/lib/active_record/test_fixtures.rb +225 -0
  171. data/lib/active_record/timestamp.rb +39 -25
  172. data/lib/active_record/touch_later.rb +4 -2
  173. data/lib/active_record/transactions.rb +56 -65
  174. data/lib/active_record/translation.rb +1 -1
  175. data/lib/active_record/type.rb +3 -4
  176. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  177. data/lib/active_record/type_caster/connection.rb +15 -14
  178. data/lib/active_record/type_caster/map.rb +1 -4
  179. data/lib/active_record/validations.rb +1 -0
  180. data/lib/active_record/validations/uniqueness.rb +15 -27
  181. data/lib/arel.rb +58 -0
  182. data/lib/arel/alias_predication.rb +9 -0
  183. data/lib/arel/attributes.rb +22 -0
  184. data/lib/arel/attributes/attribute.rb +37 -0
  185. data/lib/arel/collectors/bind.rb +24 -0
  186. data/lib/arel/collectors/composite.rb +31 -0
  187. data/lib/arel/collectors/plain_string.rb +20 -0
  188. data/lib/arel/collectors/sql_string.rb +20 -0
  189. data/lib/arel/collectors/substitute_binds.rb +28 -0
  190. data/lib/arel/crud.rb +42 -0
  191. data/lib/arel/delete_manager.rb +18 -0
  192. data/lib/arel/errors.rb +9 -0
  193. data/lib/arel/expressions.rb +29 -0
  194. data/lib/arel/factory_methods.rb +49 -0
  195. data/lib/arel/insert_manager.rb +49 -0
  196. data/lib/arel/math.rb +45 -0
  197. data/lib/arel/nodes.rb +68 -0
  198. data/lib/arel/nodes/and.rb +32 -0
  199. data/lib/arel/nodes/ascending.rb +23 -0
  200. data/lib/arel/nodes/binary.rb +52 -0
  201. data/lib/arel/nodes/bind_param.rb +36 -0
  202. data/lib/arel/nodes/case.rb +55 -0
  203. data/lib/arel/nodes/casted.rb +50 -0
  204. data/lib/arel/nodes/comment.rb +29 -0
  205. data/lib/arel/nodes/count.rb +12 -0
  206. data/lib/arel/nodes/delete_statement.rb +45 -0
  207. data/lib/arel/nodes/descending.rb +23 -0
  208. data/lib/arel/nodes/equality.rb +18 -0
  209. data/lib/arel/nodes/extract.rb +24 -0
  210. data/lib/arel/nodes/false.rb +16 -0
  211. data/lib/arel/nodes/full_outer_join.rb +8 -0
  212. data/lib/arel/nodes/function.rb +44 -0
  213. data/lib/arel/nodes/grouping.rb +8 -0
  214. data/lib/arel/nodes/in.rb +8 -0
  215. data/lib/arel/nodes/infix_operation.rb +80 -0
  216. data/lib/arel/nodes/inner_join.rb +8 -0
  217. data/lib/arel/nodes/insert_statement.rb +37 -0
  218. data/lib/arel/nodes/join_source.rb +20 -0
  219. data/lib/arel/nodes/matches.rb +18 -0
  220. data/lib/arel/nodes/named_function.rb +23 -0
  221. data/lib/arel/nodes/node.rb +50 -0
  222. data/lib/arel/nodes/node_expression.rb +13 -0
  223. data/lib/arel/nodes/outer_join.rb +8 -0
  224. data/lib/arel/nodes/over.rb +15 -0
  225. data/lib/arel/nodes/regexp.rb +16 -0
  226. data/lib/arel/nodes/right_outer_join.rb +8 -0
  227. data/lib/arel/nodes/select_core.rb +67 -0
  228. data/lib/arel/nodes/select_statement.rb +41 -0
  229. data/lib/arel/nodes/sql_literal.rb +16 -0
  230. data/lib/arel/nodes/string_join.rb +11 -0
  231. data/lib/arel/nodes/table_alias.rb +27 -0
  232. data/lib/arel/nodes/terminal.rb +16 -0
  233. data/lib/arel/nodes/true.rb +16 -0
  234. data/lib/arel/nodes/unary.rb +45 -0
  235. data/lib/arel/nodes/unary_operation.rb +20 -0
  236. data/lib/arel/nodes/unqualified_column.rb +22 -0
  237. data/lib/arel/nodes/update_statement.rb +41 -0
  238. data/lib/arel/nodes/values_list.rb +9 -0
  239. data/lib/arel/nodes/window.rb +126 -0
  240. data/lib/arel/nodes/with.rb +11 -0
  241. data/lib/arel/order_predications.rb +13 -0
  242. data/lib/arel/predications.rb +257 -0
  243. data/lib/arel/select_manager.rb +271 -0
  244. data/lib/arel/table.rb +110 -0
  245. data/lib/arel/tree_manager.rb +72 -0
  246. data/lib/arel/update_manager.rb +34 -0
  247. data/lib/arel/visitors.rb +20 -0
  248. data/lib/arel/visitors/depth_first.rb +204 -0
  249. data/lib/arel/visitors/dot.rb +297 -0
  250. data/lib/arel/visitors/ibm_db.rb +34 -0
  251. data/lib/arel/visitors/informix.rb +62 -0
  252. data/lib/arel/visitors/mssql.rb +157 -0
  253. data/lib/arel/visitors/mysql.rb +83 -0
  254. data/lib/arel/visitors/oracle.rb +159 -0
  255. data/lib/arel/visitors/oracle12.rb +66 -0
  256. data/lib/arel/visitors/postgresql.rb +110 -0
  257. data/lib/arel/visitors/sqlite.rb +39 -0
  258. data/lib/arel/visitors/to_sql.rb +889 -0
  259. data/lib/arel/visitors/visitor.rb +46 -0
  260. data/lib/arel/visitors/where_sql.rb +23 -0
  261. data/lib/arel/window_predications.rb +9 -0
  262. data/lib/rails/generators/active_record/migration.rb +14 -1
  263. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  264. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  265. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  266. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  267. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  268. metadata +109 -24
  269. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -13,33 +13,37 @@ module ActiveRecord
13
13
  class_attribute :aggregate_reflections, instance_writer: false, default: {}
14
14
  end
15
15
 
16
- def self.create(macro, name, scope, options, ar)
17
- klass = \
18
- case macro
19
- when :composed_of
20
- AggregateReflection
21
- when :has_many
22
- HasManyReflection
23
- when :has_one
24
- HasOneReflection
25
- when :belongs_to
26
- BelongsToReflection
27
- else
28
- raise "Unsupported Macro: #{macro}"
29
- end
16
+ class << self
17
+ def create(macro, name, scope, options, ar)
18
+ reflection = reflection_class_for(macro).new(name, scope, options, ar)
19
+ options[:through] ? ThroughReflection.new(reflection) : reflection
20
+ end
30
21
 
31
- reflection = klass.new(name, scope, options, ar)
32
- options[:through] ? ThroughReflection.new(reflection) : reflection
33
- end
22
+ def add_reflection(ar, name, reflection)
23
+ ar.clear_reflections_cache
24
+ name = -name.to_s
25
+ ar._reflections = ar._reflections.except(name).merge!(name => reflection)
26
+ end
34
27
 
35
- def self.add_reflection(ar, name, reflection)
36
- ar.clear_reflections_cache
37
- name = name.to_s
38
- ar._reflections = ar._reflections.except(name).merge!(name => reflection)
39
- end
28
+ def add_aggregate_reflection(ar, name, reflection)
29
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
30
+ end
40
31
 
41
- def self.add_aggregate_reflection(ar, name, reflection)
42
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
32
+ private
33
+ def reflection_class_for(macro)
34
+ case macro
35
+ when :composed_of
36
+ AggregateReflection
37
+ when :has_many
38
+ HasManyReflection
39
+ when :has_one
40
+ HasOneReflection
41
+ when :belongs_to
42
+ BelongsToReflection
43
+ else
44
+ raise "Unsupported Macro: #{macro}"
45
+ end
46
+ end
43
47
  end
44
48
 
45
49
  # \Reflection enables the ability to examine the associations and aggregations of
@@ -413,7 +417,7 @@ module ActiveRecord
413
417
  class AssociationReflection < MacroReflection #:nodoc:
414
418
  def compute_class(name)
415
419
  if polymorphic?
416
- raise ArgumentError, "Polymorphic association does not support to compute class."
420
+ raise ArgumentError, "Polymorphic associations do not support computing the class."
417
421
  end
418
422
  active_record.send(:compute_type, name)
419
423
  end
@@ -473,7 +477,7 @@ module ActiveRecord
473
477
  def check_preloadable!
474
478
  return unless scope
475
479
 
476
- if scope.arity > 0
480
+ unless scope.arity == 0
477
481
  raise ArgumentError, <<-MSG.squish
478
482
  The association scope '#{name}' is instance dependent (the scope
479
483
  block takes an argument). Preloading instance dependent scopes is
@@ -961,16 +965,14 @@ module ActiveRecord
961
965
  collect_join_reflections(seed + [self])
962
966
  end
963
967
 
964
- # TODO Change this to private once we've dropped Ruby 2.2 support.
965
- # Workaround for Ruby 2.2 "private attribute?" warning.
966
968
  protected
967
- attr_reader :delegate_reflection
968
-
969
969
  def actual_source_reflection # FIXME: this is a horrible name
970
970
  source_reflection.actual_source_reflection
971
971
  end
972
972
 
973
973
  private
974
+ attr_reader :delegate_reflection
975
+
974
976
  def collect_join_reflections(seed)
975
977
  a = source_reflection.add_as_source seed
976
978
  if options[:source_type]
@@ -5,10 +5,11 @@ module ActiveRecord
5
5
  class Relation
6
6
  MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
7
7
  :order, :joins, :left_outer_joins, :references,
8
- :extending, :unscope]
8
+ :extending, :unscope, :optimizer_hints, :annotate]
9
9
 
10
10
  SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
11
11
  :reverse_order, :distinct, :create_with, :skip_query_cache]
12
+
12
13
  CLAUSE_METHODS = [:where, :having, :from]
13
14
  INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
14
15
 
@@ -18,6 +19,7 @@ module ActiveRecord
18
19
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
19
20
 
20
21
  attr_reader :table, :klass, :loaded, :predicate_builder
22
+ attr_accessor :skip_preloading_value
21
23
  alias :model :klass
22
24
  alias :loaded? :loaded
23
25
  alias :locked? :lock_value
@@ -41,6 +43,17 @@ module ActiveRecord
41
43
  klass.arel_attribute(name, table)
42
44
  end
43
45
 
46
+ def bind_attribute(name, value) # :nodoc:
47
+ if reflection = klass._reflect_on_association(name)
48
+ name = reflection.foreign_key
49
+ value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
50
+ end
51
+
52
+ attr = arel_attribute(name)
53
+ bind = predicate_builder.build_bind_attribute(attr.name, value)
54
+ yield attr, bind
55
+ end
56
+
44
57
  # Initializes new record from relation while maintaining the current
45
58
  # scope.
46
59
  #
@@ -54,7 +67,8 @@ module ActiveRecord
54
67
  # user = users.new { |user| user.name = 'Oscar' }
55
68
  # user.name # => Oscar
56
69
  def new(attributes = nil, &block)
57
- scoping { klass.new(values_for_create(attributes), &block) }
70
+ block = _deprecated_scope_block("new", &block)
71
+ scoping { klass.new(attributes, &block) }
58
72
  end
59
73
 
60
74
  alias build new
@@ -82,7 +96,8 @@ module ActiveRecord
82
96
  if attributes.is_a?(Array)
83
97
  attributes.collect { |attr| create(attr, &block) }
84
98
  else
85
- scoping { klass.create(values_for_create(attributes), &block) }
99
+ block = _deprecated_scope_block("create", &block)
100
+ scoping { klass.create(attributes, &block) }
86
101
  end
87
102
  end
88
103
 
@@ -96,7 +111,8 @@ module ActiveRecord
96
111
  if attributes.is_a?(Array)
97
112
  attributes.collect { |attr| create!(attr, &block) }
98
113
  else
99
- scoping { klass.create!(values_for_create(attributes), &block) }
114
+ block = _deprecated_scope_block("create!", &block)
115
+ scoping { klass.create!(attributes, &block) }
100
116
  end
101
117
  end
102
118
 
@@ -143,23 +159,12 @@ module ActiveRecord
143
159
  # failed due to validation errors it won't be persisted, you get what
144
160
  # #create returns in such situation.
145
161
  #
146
- # Please note *this method is not atomic*, it runs first a SELECT, and if
162
+ # Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
147
163
  # there are no results an INSERT is attempted. If there are other threads
148
164
  # or processes there is a race condition between both calls and it could
149
165
  # be the case that you end up with two similar records.
150
166
  #
151
- # Whether that is a problem or not depends on the logic of the
152
- # application, but in the particular case in which rows have a UNIQUE
153
- # constraint an exception may be raised, just retry:
154
- #
155
- # begin
156
- # CreditAccount.transaction(requires_new: true) do
157
- # CreditAccount.find_or_create_by(user_id: user.id)
158
- # end
159
- # rescue ActiveRecord::RecordNotUnique
160
- # retry
161
- # end
162
- #
167
+ # If this might be a problem for your application, please see #create_or_find_by.
163
168
  def find_or_create_by(attributes, &block)
164
169
  find_by(attributes) || create(attributes, &block)
165
170
  end
@@ -171,6 +176,51 @@ module ActiveRecord
171
176
  find_by(attributes) || create!(attributes, &block)
172
177
  end
173
178
 
179
+ # Attempts to create a record with the given attributes in a table that has a unique constraint
180
+ # on one or several of its columns. If a row already exists with one or several of these
181
+ # unique constraints, the exception such an insertion would normally raise is caught,
182
+ # and the existing record with those attributes is found using #find_by!.
183
+ #
184
+ # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
185
+ # and the INSERT, as that method needs to first query the table, then attempt to insert a row
186
+ # if none is found.
187
+ #
188
+ # There are several drawbacks to #create_or_find_by, though:
189
+ #
190
+ # * The underlying table must have the relevant columns defined with unique constraints.
191
+ # * A unique constraint violation may be triggered by only one, or at least less than all,
192
+ # of the given attributes. This means that the subsequent #find_by! may fail to find a
193
+ # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
194
+ # rather than a record with the given attributes.
195
+ # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
196
+ # we actually have another race condition between INSERT -> SELECT, which can be triggered
197
+ # if a DELETE between those two statements is run by another client. But for most applications,
198
+ # that's a significantly less likely condition to hit.
199
+ # * It relies on exception handling to handle control flow, which may be marginally slower.
200
+ # * The primary key may auto-increment on each create, even if it fails. This can accelerate
201
+ # the problem of running out of integers, if the underlying table is still stuck on a primary
202
+ # key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
203
+ # to this problem).
204
+ #
205
+ # This method will return a record if all given attributes are covered by unique constraints
206
+ # (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
207
+ # and failed due to validation errors it won't be persisted, you get what #create returns in
208
+ # such situation.
209
+ def create_or_find_by(attributes, &block)
210
+ transaction(requires_new: true) { create(attributes, &block) }
211
+ rescue ActiveRecord::RecordNotUnique
212
+ find_by!(attributes)
213
+ end
214
+
215
+ # Like #create_or_find_by, but calls
216
+ # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
217
+ # is raised if the created record is invalid.
218
+ def create_or_find_by!(attributes, &block)
219
+ transaction(requires_new: true) { create!(attributes, &block) }
220
+ rescue ActiveRecord::RecordNotUnique
221
+ find_by!(attributes)
222
+ end
223
+
174
224
  # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
175
225
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
176
226
  def find_or_initialize_by(attributes, &block)
@@ -185,7 +235,7 @@ module ActiveRecord
185
235
  # are needed by the next ones when eager loading is going on.
186
236
  #
187
237
  # Please see further details in the
188
- # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
238
+ # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
189
239
  def explain
190
240
  exec_explain(collecting_queries_for_explain { exec_queries })
191
241
  end
@@ -241,32 +291,100 @@ module ActiveRecord
241
291
  limit_value ? records.many? : size > 1
242
292
  end
243
293
 
244
- # Returns a cache key that can be used to identify the records fetched by
245
- # this query. The cache key is built with a fingerprint of the sql query,
246
- # the number of records matched by the query and a timestamp of the last
247
- # updated record. When a new record comes to match the query, or any of
248
- # the existing records is updated or deleted, the cache key changes.
294
+ # Returns a stable cache key that can be used to identify this query.
295
+ # The cache key is built with a fingerprint of the SQL query.
249
296
  #
250
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
251
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
297
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
298
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
252
299
  #
253
- # If the collection is loaded, the method will iterate through the records
254
- # to generate the timestamp, otherwise it will trigger one SQL query like:
300
+ # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
301
+ # in Rails 6.0 and earlier, the cache key will also include a version.
255
302
  #
256
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
303
+ # ActiveRecord::Base.collection_cache_versioning = false
304
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
305
+ # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
257
306
  #
258
307
  # You can also pass a custom timestamp column to fetch the timestamp of the
259
308
  # last updated record.
260
309
  #
261
310
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
262
- #
263
- # You can customize the strategy to generate the key on a per model basis
264
- # overriding ActiveRecord::Base#collection_cache_key.
265
311
  def cache_key(timestamp_column = :updated_at)
266
312
  @cache_keys ||= {}
267
- @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
313
+ @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
314
+ end
315
+
316
+ def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
317
+ query_signature = ActiveSupport::Digest.hexdigest(to_sql)
318
+ key = "#{klass.model_name.cache_key}/query-#{query_signature}"
319
+
320
+ if cache_version(timestamp_column)
321
+ key
322
+ else
323
+ "#{key}-#{compute_cache_version(timestamp_column)}"
324
+ end
325
+ end
326
+ private :compute_cache_key
327
+
328
+ # Returns a cache version that can be used together with the cache key to form
329
+ # a recyclable caching scheme. The cache version is built with the number of records
330
+ # matching the query, and the timestamp of the last updated record. When a new record
331
+ # comes to match the query, or any of the existing records is updated or deleted,
332
+ # the cache version changes.
333
+ #
334
+ # If the collection is loaded, the method will iterate through the records
335
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
336
+ #
337
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
338
+ def cache_version(timestamp_column = :updated_at)
339
+ if collection_cache_versioning
340
+ @cache_versions ||= {}
341
+ @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
342
+ end
268
343
  end
269
344
 
345
+ def compute_cache_version(timestamp_column) # :nodoc:
346
+ if loaded? || distinct_value
347
+ size = records.size
348
+ if size > 0
349
+ timestamp = max_by(&timestamp_column)._read_attribute(timestamp_column)
350
+ end
351
+ else
352
+ collection = eager_loading? ? apply_join_dependency : self
353
+
354
+ column = connection.visitor.compile(arel_attribute(timestamp_column))
355
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
356
+
357
+ if collection.has_limit_or_offset?
358
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
359
+ subquery_alias = "subquery_for_cache_key"
360
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
361
+ arel = query.build_subquery(subquery_alias, select_values % subquery_column)
362
+ else
363
+ query = collection.unscope(:order)
364
+ query.select_values = [select_values % column]
365
+ arel = query.arel
366
+ end
367
+
368
+ result = connection.select_one(arel, nil)
369
+
370
+ if result
371
+ column_type = klass.type_for_attribute(timestamp_column)
372
+ timestamp = column_type.deserialize(result["timestamp"])
373
+ size = result["size"]
374
+ else
375
+ timestamp = nil
376
+ size = 0
377
+ end
378
+ end
379
+
380
+ if timestamp
381
+ "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
382
+ else
383
+ "#{size}"
384
+ end
385
+ end
386
+ private :compute_cache_version
387
+
270
388
  # Scope all queries to the current scope.
271
389
  #
272
390
  # Comment.where(post_id: 1).scoping do
@@ -277,15 +395,12 @@ module ActiveRecord
277
395
  # Please check unscoped if you want to remove all previous scopes (including
278
396
  # the default_scope) during the execution of a block.
279
397
  def scoping
280
- previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
281
- yield
282
- ensure
283
- klass.current_scope = previous unless @delegate_to_klass
398
+ already_in_scope? ? yield : _scoping(self) { yield }
284
399
  end
285
400
 
286
- def _exec_scope(*args, &block) # :nodoc:
401
+ def _exec_scope(name, *args, &block) # :nodoc:
287
402
  @delegate_to_klass = true
288
- instance_exec(*args, &block) || self
403
+ _scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
289
404
  ensure
290
405
  @delegate_to_klass = false
291
406
  end
@@ -295,6 +410,8 @@ module ActiveRecord
295
410
  # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
296
411
  # Active Record's normal type casting and serialization.
297
412
  #
413
+ # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
414
+ #
298
415
  # ==== Parameters
299
416
  #
300
417
  # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
@@ -321,17 +438,23 @@ module ActiveRecord
321
438
  end
322
439
 
323
440
  stmt = Arel::UpdateManager.new
324
-
325
- stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
326
- stmt.table(table)
327
-
328
- if has_join_values? || offset_value
329
- @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
441
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
442
+ stmt.key = arel_attribute(primary_key)
443
+ stmt.take(arel.limit)
444
+ stmt.offset(arel.offset)
445
+ stmt.order(*arel.orders)
446
+ stmt.wheres = arel.constraints
447
+
448
+ if updates.is_a?(Hash)
449
+ if klass.locking_enabled? &&
450
+ !updates.key?(klass.locking_column) &&
451
+ !updates.key?(klass.locking_column.to_sym)
452
+ attr = arel_attribute(klass.locking_column)
453
+ updates[attr.name] = _increment_attribute(attr)
454
+ end
455
+ stmt.set _substitute_values(updates)
330
456
  else
331
- stmt.key = arel_attribute(primary_key)
332
- stmt.take(arel.limit)
333
- stmt.order(*arel.orders)
334
- stmt.wheres = arel.constraints
457
+ stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
335
458
  end
336
459
 
337
460
  @klass.connection.update stmt, "#{@klass} Update All"
@@ -345,6 +468,51 @@ module ActiveRecord
345
468
  end
346
469
  end
347
470
 
471
+ def update_counters(counters) # :nodoc:
472
+ touch = counters.delete(:touch)
473
+
474
+ updates = {}
475
+ counters.each do |counter_name, value|
476
+ attr = arel_attribute(counter_name)
477
+ updates[attr.name] = _increment_attribute(attr, value)
478
+ end
479
+
480
+ if touch
481
+ names = touch if touch != true
482
+ touch_updates = klass.touch_attributes_with_time(*names)
483
+ updates.merge!(touch_updates) unless touch_updates.empty?
484
+ end
485
+
486
+ update_all updates
487
+ end
488
+
489
+ # Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
490
+ # set to the current time or the time specified.
491
+ # This method can be passed attribute names and an optional time argument.
492
+ # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
493
+ # If no time argument is passed, the current time is used as default.
494
+ #
495
+ # === Examples
496
+ #
497
+ # # Touch all records
498
+ # Person.all.touch_all
499
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
500
+ #
501
+ # # Touch multiple records with a custom attribute
502
+ # Person.all.touch_all(:created_at)
503
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
504
+ #
505
+ # # Touch multiple records with a specified time
506
+ # Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
507
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
508
+ #
509
+ # # Touch records with scope
510
+ # Person.where(name: 'David').touch_all
511
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
512
+ def touch_all(*names, time: nil)
513
+ update_all klass.touch_attributes_with_time(*names, time: time)
514
+ end
515
+
348
516
  # Destroys the records by instantiating each
349
517
  # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
350
518
  # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
@@ -385,8 +553,8 @@ module ActiveRecord
385
553
  # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
386
554
  def delete_all
387
555
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
388
- value = get_value(method)
389
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
556
+ value = @values[method]
557
+ method == :distinct ? value : value&.any?
390
558
  end
391
559
  if invalid_methods.any?
392
560
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
@@ -398,13 +566,12 @@ module ActiveRecord
398
566
  end
399
567
 
400
568
  stmt = Arel::DeleteManager.new
401
- stmt.from(table)
402
-
403
- if has_join_values? || has_limit_or_offset?
404
- @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
405
- else
406
- stmt.wheres = arel.constraints
407
- end
569
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
570
+ stmt.key = arel_attribute(primary_key)
571
+ stmt.take(arel.limit)
572
+ stmt.offset(arel.offset)
573
+ stmt.order(*arel.orders)
574
+ stmt.wheres = arel.constraints
408
575
 
409
576
  affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
410
577
 
@@ -412,6 +579,32 @@ module ActiveRecord
412
579
  affected
413
580
  end
414
581
 
582
+ # Finds and destroys all records matching the specified conditions.
583
+ # This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
584
+ # Returns the collection of objects that were destroyed.
585
+ #
586
+ # If no record is found, returns empty array.
587
+ #
588
+ # Person.destroy_by(id: 13)
589
+ # Person.destroy_by(name: 'Spartacus', rating: 4)
590
+ # Person.destroy_by("published_at < ?", 2.weeks.ago)
591
+ def destroy_by(*args)
592
+ where(*args).destroy_all
593
+ end
594
+
595
+ # Finds and deletes all records matching the specified conditions.
596
+ # This is short-hand for <tt>relation.where(condition).delete_all</tt>.
597
+ # Returns the number of rows affected.
598
+ #
599
+ # If no record is found, returns <tt>0</tt> as zero rows were affected.
600
+ #
601
+ # Person.delete_by(id: 13)
602
+ # Person.delete_by(name: 'Spartacus', rating: 4)
603
+ # Person.delete_by("published_at < ?", 2.weeks.ago)
604
+ def delete_by(*args)
605
+ where(*args).delete_all
606
+ end
607
+
415
608
  # Causes the records to be loaded from the database if they have not
416
609
  # been loaded already. You can use this if for some reason you need
417
610
  # to explicitly load some records before actually using them. The
@@ -432,9 +625,11 @@ module ActiveRecord
432
625
 
433
626
  def reset
434
627
  @delegate_to_klass = false
628
+ @_deprecated_scope_source = nil
435
629
  @to_sql = @arel = @loaded = @should_eager_load = nil
436
630
  @records = [].freeze
437
631
  @offsets = {}
632
+ @take = nil
438
633
  self
439
634
  end
440
635
 
@@ -530,17 +725,72 @@ module ActiveRecord
530
725
  ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
531
726
  end
532
727
 
728
+ def preload_associations(records) # :nodoc:
729
+ preload = preload_values
730
+ preload += includes_values unless eager_loading?
731
+ preloader = nil
732
+ preload.each do |associations|
733
+ preloader ||= build_preloader
734
+ preloader.preload records, associations
735
+ end
736
+ end
737
+
738
+ attr_reader :_deprecated_scope_source # :nodoc:
739
+
533
740
  protected
741
+ attr_writer :_deprecated_scope_source # :nodoc:
534
742
 
535
743
  def load_records(records)
536
744
  @records = records.freeze
537
745
  @loaded = true
538
746
  end
539
747
 
748
+ def null_relation? # :nodoc:
749
+ is_a?(NullRelation)
750
+ end
751
+
540
752
  private
753
+ def already_in_scope?
754
+ @delegate_to_klass && begin
755
+ scope = klass.current_scope(true)
756
+ scope && !scope._deprecated_scope_source
757
+ end
758
+ end
759
+
760
+ def _deprecated_spawn(name)
761
+ spawn.tap { |scope| scope._deprecated_scope_source = name }
762
+ end
763
+
764
+ def _deprecated_scope_block(name, &block)
765
+ -> record do
766
+ klass.current_scope = _deprecated_spawn(name)
767
+ yield record if block_given?
768
+ end
769
+ end
541
770
 
542
- def has_join_values?
543
- joins_values.any? || left_outer_joins_values.any?
771
+ def _scoping(scope)
772
+ previous, klass.current_scope = klass.current_scope(true), scope
773
+ yield
774
+ ensure
775
+ klass.current_scope = previous
776
+ end
777
+
778
+ def _substitute_values(values)
779
+ values.map do |name, value|
780
+ attr = arel_attribute(name)
781
+ unless Arel.arel_node?(value)
782
+ type = klass.type_for_attribute(attr.name)
783
+ value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
784
+ end
785
+ [attr, value]
786
+ end
787
+ end
788
+
789
+ def _increment_attribute(attribute, value = 1)
790
+ bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
791
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
792
+ expr = value < 0 ? expr - bind : expr + bind
793
+ expr.expr
544
794
  end
545
795
 
546
796
  def exec_queries(&block)
@@ -548,7 +798,7 @@ module ActiveRecord
548
798
  @records =
549
799
  if eager_loading?
550
800
  apply_join_dependency do |relation, join_dependency|
551
- if ActiveRecord::NullRelation === relation
801
+ if relation.null_relation?
552
802
  []
553
803
  else
554
804
  relation = join_dependency.apply_column_aliases(relation)
@@ -560,13 +810,7 @@ module ActiveRecord
560
810
  klass.find_by_sql(arel, &block).freeze
561
811
  end
562
812
 
563
- preload = preload_values
564
- preload += includes_values unless eager_loading?
565
- preloader = nil
566
- preload.each do |associations|
567
- preloader ||= build_preloader
568
- preloader.preload @records, associations
569
- end
813
+ preload_associations(@records) unless skip_preloading_value
570
814
 
571
815
  @records.each(&:readonly!) if readonly_value
572
816
 
@@ -612,18 +856,5 @@ module ActiveRecord
612
856
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
613
857
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
614
858
  end
615
-
616
- def values_for_create(attributes = nil)
617
- result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
618
-
619
- # NOTE: if there are same keys in both create_with and result, create_with should be used.
620
- # This is to make sure nested attributes don't get passed to the klass.new,
621
- # while keeping the precedence of the duplicate keys in create_with.
622
- create_with_value.stringify_keys.each do |k, v|
623
- result[k] = v if result.key?(k)
624
- end
625
-
626
- result
627
- end
628
859
  end
629
860
  end