activerecord 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. data/lib/active_record/null_relation.rb +0 -63
@@ -57,6 +57,36 @@ module ActiveRecord
57
57
  end
58
58
  end
59
59
 
60
+ # Builds an object (or multiple objects) and returns either the built object or a list of built
61
+ # objects.
62
+ #
63
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
64
+ # attributes on the objects that are to be built.
65
+ #
66
+ # ==== Examples
67
+ # # Build a single new object
68
+ # User.build(first_name: 'Jamie')
69
+ #
70
+ # # Build an Array of new objects
71
+ # User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
72
+ #
73
+ # # Build a single object and pass it into a block to set other attributes.
74
+ # User.build(first_name: 'Jamie') do |u|
75
+ # u.is_admin = false
76
+ # end
77
+ #
78
+ # # Building an Array of new objects using a block, where the block is executed for each object:
79
+ # User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
80
+ # u.is_admin = false
81
+ # end
82
+ def build(attributes = nil, &block)
83
+ if attributes.is_a?(Array)
84
+ attributes.collect { |attr| build(attr, &block) }
85
+ else
86
+ new(attributes, &block)
87
+ end
88
+ end
89
+
60
90
  # Inserts a single record into the database in a single SQL INSERT
61
91
  # statement. It does not instantiate any models nor does it trigger
62
92
  # Active Record callbacks or validations. Though passed values
@@ -85,14 +115,14 @@ module ActiveRecord
85
115
  # ==== Options
86
116
  #
87
117
  # [:returning]
88
- # (PostgreSQL only) An array of attributes to return for all successfully
118
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
89
119
  # inserted records, which by default is the primary key.
90
120
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
91
121
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
92
122
  # clause entirely.
93
123
  #
94
124
  # You can also pass an SQL string if you need more control on the return values
95
- # (for example, <tt>returning: "id, name as new_name"</tt>).
125
+ # (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
96
126
  #
97
127
  # [:unique_by]
98
128
  # (PostgreSQL and SQLite only) By default rows are considered to be unique
@@ -102,7 +132,7 @@ module ActiveRecord
102
132
  #
103
133
  # Consider a Book model where no duplicate ISBNs make sense, but if any
104
134
  # row has an existing id, or is not unique by another unique index,
105
- # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
135
+ # ActiveRecord::RecordNotUnique is raised.
106
136
  #
107
137
  # Unique indexes can be identified by columns or name:
108
138
  #
@@ -164,7 +194,7 @@ module ActiveRecord
164
194
  # The +attributes+ parameter is an Array of Hashes. Every Hash determines
165
195
  # the attributes for a single row and must have the same keys.
166
196
  #
167
- # Raises <tt>ActiveRecord::RecordNotUnique</tt> if any rows violate a
197
+ # Raises ActiveRecord::RecordNotUnique if any rows violate a
168
198
  # unique index on the table. In that case, no rows are inserted.
169
199
  #
170
200
  # To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
@@ -175,14 +205,14 @@ module ActiveRecord
175
205
  # ==== Options
176
206
  #
177
207
  # [:returning]
178
- # (PostgreSQL only) An array of attributes to return for all successfully
208
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
179
209
  # inserted records, which by default is the primary key.
180
210
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
181
211
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
182
212
  # clause entirely.
183
213
  #
184
214
  # You can also pass an SQL string if you need more control on the return values
185
- # (for example, <tt>returning: "id, name as new_name"</tt>).
215
+ # (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
186
216
  #
187
217
  # [:record_timestamps]
188
218
  # By default, automatic setting of timestamp columns is controlled by
@@ -219,8 +249,8 @@ module ActiveRecord
219
249
  # go through Active Record's type casting and serialization.
220
250
  #
221
251
  # See #upsert_all for documentation.
222
- def upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil, record_timestamps: nil)
223
- upsert_all([ attributes ], on_duplicate: on_duplicate, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
252
+ def upsert(attributes, **kwargs)
253
+ upsert_all([ attributes ], **kwargs)
224
254
  end
225
255
 
226
256
  # Updates or inserts (upserts) multiple records into the database in a
@@ -241,14 +271,14 @@ module ActiveRecord
241
271
  # ==== Options
242
272
  #
243
273
  # [:returning]
244
- # (PostgreSQL only) An array of attributes to return for all successfully
274
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
245
275
  # inserted records, which by default is the primary key.
246
276
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
247
277
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
248
278
  # clause entirely.
249
279
  #
250
280
  # You can also pass an SQL string if you need more control on the return values
251
- # (for example, <tt>returning: "id, name as new_name"</tt>).
281
+ # (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
252
282
  #
253
283
  # [:unique_by]
254
284
  # (PostgreSQL and SQLite only) By default rows are considered to be unique
@@ -258,7 +288,7 @@ module ActiveRecord
258
288
  #
259
289
  # Consider a Book model where no duplicate ISBNs make sense, but if any
260
290
  # row has an existing id, or is not unique by another unique index,
261
- # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
291
+ # ActiveRecord::RecordNotUnique is raised.
262
292
  #
263
293
  # Unique indexes can be identified by columns or name:
264
294
  #
@@ -425,6 +455,62 @@ module ActiveRecord
425
455
  end
426
456
  end
427
457
 
458
+ # Accepts a list of attribute names to be used in the WHERE clause
459
+ # of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for +#first+ and +#last+ finder methods.
460
+ #
461
+ # class Developer < ActiveRecord::Base
462
+ # query_constraints :company_id, :id
463
+ # end
464
+ #
465
+ # developer = Developer.first
466
+ # # SELECT "developers".* FROM "developers" ORDER BY "developers"."company_id" ASC, "developers"."id" ASC LIMIT 1
467
+ # developer.inspect # => #<Developer id: 1, company_id: 1, ...>
468
+ #
469
+ # developer.update!(name: "Nikita")
470
+ # # UPDATE "developers" SET "name" = 'Nikita' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
471
+ #
472
+ # # It is possible to update an attribute used in the query_constraints clause:
473
+ # developer.update!(company_id: 2)
474
+ # # UPDATE "developers" SET "company_id" = 2 WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
475
+ #
476
+ # developer.name = "Bob"
477
+ # developer.save!
478
+ # # UPDATE "developers" SET "name" = 'Bob' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
479
+ #
480
+ # developer.destroy!
481
+ # # DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
482
+ #
483
+ # developer.delete
484
+ # # DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
485
+ #
486
+ # developer.reload
487
+ # # SELECT "developers".* FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 LIMIT 1
488
+ def query_constraints(*columns_list)
489
+ raise ArgumentError, "You must specify at least one column to be used in querying" if columns_list.empty?
490
+
491
+ @query_constraints_list = columns_list.map(&:to_s)
492
+ @has_query_constraints = @query_constraints_list
493
+ end
494
+
495
+ def has_query_constraints? # :nodoc:
496
+ @has_query_constraints
497
+ end
498
+
499
+ def query_constraints_list # :nodoc:
500
+ @query_constraints_list ||= if base_class? || primary_key != base_class.primary_key
501
+ primary_key if primary_key.is_a?(Array)
502
+ else
503
+ base_class.query_constraints_list
504
+ end
505
+ end
506
+
507
+ # Returns an array of column names to be used in queries. The source of column
508
+ # names is derived from +query_constraints_list+ or +primary_key+. This method
509
+ # is for internal use when the primary key is to be treated as an array.
510
+ def composite_query_constraints_list # :nodoc:
511
+ @composite_query_constraints_list ||= query_constraints_list || Array(primary_key)
512
+ end
513
+
428
514
  # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
429
515
  # therefore all callbacks and filters are fired off before the object is deleted. This method is
430
516
  # less efficient than #delete but allows cleanup methods and other actions to be run.
@@ -445,7 +531,13 @@ module ActiveRecord
445
531
  # todos = [1,2,3]
446
532
  # Todo.destroy(todos)
447
533
  def destroy(id)
448
- if id.is_a?(Array)
534
+ multiple_ids = if composite_primary_key?
535
+ id.first.is_a?(Array)
536
+ else
537
+ id.is_a?(Array)
538
+ end
539
+
540
+ if multiple_ids
449
541
  find(id).each(&:destroy)
450
542
  else
451
543
  find(id).destroy
@@ -474,7 +566,7 @@ module ActiveRecord
474
566
  delete_by(primary_key => id_or_array)
475
567
  end
476
568
 
477
- def _insert_record(values) # :nodoc:
569
+ def _insert_record(values, returning) # :nodoc:
478
570
  primary_key = self.primary_key
479
571
  primary_key_value = nil
480
572
 
@@ -493,7 +585,10 @@ module ActiveRecord
493
585
  im.insert(values.transform_keys { |name| arel_table[name] })
494
586
  end
495
587
 
496
- connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
588
+ connection.insert(
589
+ im, "#{self} Create", primary_key || false, primary_key_value,
590
+ returning: returning
591
+ )
497
592
  end
498
593
 
499
594
  def _update_record(values, constraints) # :nodoc:
@@ -530,6 +625,14 @@ module ActiveRecord
530
625
  end
531
626
 
532
627
  private
628
+ def inherited(subclass)
629
+ super
630
+ subclass.class_eval do
631
+ @_query_constraints_list = nil
632
+ @has_query_constraints = false
633
+ end
634
+ end
635
+
533
636
  # Given a class, an attributes hash, +instantiate_instance_of+ returns a
534
637
  # new instance of the class. Accepts only keys as strings.
535
638
  def instantiate_instance_of(klass, attributes, column_types = {}, &block)
@@ -564,7 +667,7 @@ module ActiveRecord
564
667
  end
565
668
 
566
669
  # Returns true if this object was just created -- that is, prior to the last
567
- # save, the object didn't exist in the database and new_record? would have
670
+ # update or delete, the object didn't exist in the database and new_record? would have
568
671
  # returned true.
569
672
  def previously_new_record?
570
673
  @previously_new_record
@@ -663,6 +766,7 @@ module ActiveRecord
663
766
  def delete
664
767
  _delete_row if persisted?
665
768
  @destroyed = true
769
+ @previously_new_record = false
666
770
  freeze
667
771
  end
668
772
 
@@ -676,12 +780,9 @@ module ActiveRecord
676
780
  def destroy
677
781
  _raise_readonly_record_error if readonly?
678
782
  destroy_associations
679
- @_trigger_destroy_callback = if persisted?
680
- destroy_row > 0
681
- else
682
- true
683
- end
783
+ @_trigger_destroy_callback ||= persisted? && destroy_row > 0
684
784
  @destroyed = true
785
+ @previously_new_record = false
685
786
  freeze
686
787
  end
687
788
 
@@ -707,11 +808,14 @@ module ActiveRecord
707
808
  # Note: The new instance will share a link to the same attributes as the original class.
708
809
  # Therefore the STI column value will still be the same.
709
810
  # Any change to the attributes on either instance will affect both instances.
811
+ # This includes any attribute initialization done by the new instance.
812
+ #
710
813
  # If you want to change the STI column as well, use #becomes! instead.
711
814
  def becomes(klass)
712
815
  became = klass.allocate
713
816
 
714
817
  became.send(:initialize) do |becoming|
818
+ @attributes.reverse_merge!(becoming.instance_variable_get(:@attributes))
715
819
  becoming.instance_variable_set(:@attributes, @attributes)
716
820
  becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
717
821
  becoming.instance_variable_set(:@new_record, new_record?)
@@ -746,7 +850,7 @@ module ActiveRecord
746
850
  # * updated_at/updated_on column is updated if that column is available.
747
851
  # * Updates all the attributes that are dirty in this object.
748
852
  #
749
- # This method raises an ActiveRecord::ActiveRecordError if the
853
+ # This method raises an ActiveRecord::ActiveRecordError if the
750
854
  # attribute is marked as readonly.
751
855
  #
752
856
  # Also see #update_column.
@@ -758,6 +862,28 @@ module ActiveRecord
758
862
  save(validate: false)
759
863
  end
760
864
 
865
+ # Updates a single attribute and saves the record.
866
+ # This is especially useful for boolean flags on existing records. Also note that
867
+ #
868
+ # * Validation is skipped.
869
+ # * \Callbacks are invoked.
870
+ # * updated_at/updated_on column is updated if that column is available.
871
+ # * Updates all the attributes that are dirty in this object.
872
+ #
873
+ # This method raises an ActiveRecord::ActiveRecordError if the
874
+ # attribute is marked as readonly.
875
+ #
876
+ # If any of the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
877
+ # and #update_attribute! raises ActiveRecord::RecordNotSaved. See
878
+ # ActiveRecord::Callbacks for further details.
879
+ def update_attribute!(name, value)
880
+ name = name.to_s
881
+ verify_readonly_attribute(name)
882
+ public_send("#{name}=", value)
883
+
884
+ save!(validate: false)
885
+ end
886
+
761
887
  # Updates the attributes of the model from the passed-in hash and saves the
762
888
  # record, all wrapped in a transaction. If the object is invalid, the saving
763
889
  # will fail and false will be returned.
@@ -813,7 +939,7 @@ module ActiveRecord
813
939
  verify_readonly_attribute(name) || name
814
940
  end
815
941
 
816
- update_constraints = _primary_key_constraints_hash
942
+ update_constraints = _query_constraints_hash
817
943
  attributes = attributes.each_with_object({}) do |(k, v), h|
818
944
  h[k] = @attributes.write_cast_value(k, v)
819
945
  clear_attribute_change(k)
@@ -944,12 +1070,13 @@ module ActiveRecord
944
1070
  self.class.connection.clear_query_cache
945
1071
 
946
1072
  fresh_object = if apply_scoping?(options)
947
- _find_record(options)
1073
+ _find_record((options || {}).merge(all_queries: true))
948
1074
  else
949
1075
  self.class.unscoped { _find_record(options) }
950
1076
  end
951
1077
 
952
1078
  @association_cache = fresh_object.instance_variable_get(:@association_cache)
1079
+ @association_cache.each_value { |association| association.owner = self }
953
1080
  @attributes = fresh_object.instance_variable_get(:@attributes)
954
1081
  @new_record = false
955
1082
  @previously_new_record = false
@@ -995,10 +1122,12 @@ module ActiveRecord
995
1122
  _raise_readonly_record_error if readonly?
996
1123
 
997
1124
  attribute_names = timestamp_attributes_for_update_in_model
998
- attribute_names |= names.map! do |name|
1125
+ attribute_names = (attribute_names | names).map! do |name|
999
1126
  name = name.to_s
1000
- self.class.attribute_aliases[name] || name
1001
- end unless names.empty?
1127
+ name = self.class.attribute_aliases[name] || name
1128
+ verify_readonly_attribute(name)
1129
+ name
1130
+ end
1002
1131
 
1003
1132
  unless attribute_names.empty?
1004
1133
  affected_rows = _touch_row(attribute_names, time)
@@ -1009,6 +1138,12 @@ module ActiveRecord
1009
1138
  end
1010
1139
 
1011
1140
  private
1141
+ def init_internals
1142
+ super
1143
+ @_trigger_destroy_callback = @_trigger_update_callback = nil
1144
+ @previously_new_record = false
1145
+ end
1146
+
1012
1147
  def strict_loaded_associations
1013
1148
  @association_cache.find_all do |_, assoc|
1014
1149
  assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
@@ -1016,10 +1151,23 @@ module ActiveRecord
1016
1151
  end
1017
1152
 
1018
1153
  def _find_record(options)
1154
+ all_queries = options ? options[:all_queries] : nil
1155
+ base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
1156
+
1019
1157
  if options && options[:lock]
1020
- self.class.preload(strict_loaded_associations).lock(options[:lock]).find(id)
1158
+ base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
1021
1159
  else
1022
- self.class.preload(strict_loaded_associations).find(id)
1160
+ base.find_by!(_in_memory_query_constraints_hash)
1161
+ end
1162
+ end
1163
+
1164
+ def _in_memory_query_constraints_hash
1165
+ if self.class.query_constraints_list.nil?
1166
+ { @primary_key => id }
1167
+ else
1168
+ self.class.query_constraints_list.index_with do |column_name|
1169
+ attribute(column_name)
1170
+ end
1023
1171
  end
1024
1172
  end
1025
1173
 
@@ -1028,8 +1176,14 @@ module ActiveRecord
1028
1176
  (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
1029
1177
  end
1030
1178
 
1031
- def _primary_key_constraints_hash
1032
- { @primary_key => id_in_database }
1179
+ def _query_constraints_hash
1180
+ if self.class.query_constraints_list.nil?
1181
+ { @primary_key => id_in_database }
1182
+ else
1183
+ self.class.query_constraints_list.index_with do |column_name|
1184
+ attribute_in_database(column_name)
1185
+ end
1186
+ end
1033
1187
  end
1034
1188
 
1035
1189
  # A hook to be overridden by association modules.
@@ -1041,7 +1195,7 @@ module ActiveRecord
1041
1195
  end
1042
1196
 
1043
1197
  def _delete_row
1044
- self.class._delete_record(_primary_key_constraints_hash)
1198
+ self.class._delete_record(_query_constraints_hash)
1045
1199
  end
1046
1200
 
1047
1201
  def _touch_row(attribute_names, time)
@@ -1057,7 +1211,7 @@ module ActiveRecord
1057
1211
  def _update_row(attribute_names, attempted_action = "update")
1058
1212
  self.class._update_record(
1059
1213
  attributes_with_values(attribute_names),
1060
- _primary_key_constraints_hash
1214
+ _query_constraints_hash
1061
1215
  )
1062
1216
  end
1063
1217
 
@@ -1093,11 +1247,16 @@ module ActiveRecord
1093
1247
  def _create_record(attribute_names = self.attribute_names)
1094
1248
  attribute_names = attributes_for_create(attribute_names)
1095
1249
 
1096
- new_id = self.class._insert_record(
1097
- attributes_with_values(attribute_names)
1250
+ returning_columns = self.class._returning_columns_for_insert
1251
+
1252
+ returning_values = self.class._insert_record(
1253
+ attributes_with_values(attribute_names),
1254
+ returning_columns
1098
1255
  )
1099
1256
 
1100
- self.id ||= new_id if @primary_key
1257
+ returning_columns.zip(returning_values).each do |column, value|
1258
+ _write_attribute(column, value) if !_read_attribute(column)
1259
+ end if returning_values
1101
1260
 
1102
1261
  @new_record = false
1103
1262
  @previously_new_record = true
@@ -1114,7 +1273,7 @@ module ActiveRecord
1114
1273
  def _raise_record_not_destroyed
1115
1274
  @_association_destroy_exception ||= nil
1116
1275
  key = self.class.primary_key
1117
- raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{send(key)}", self)
1276
+ raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
1118
1277
  ensure
1119
1278
  @_association_destroy_exception = nil
1120
1279
  end
@@ -1129,11 +1288,5 @@ module ActiveRecord
1129
1288
  persisted?, new_record?, or destroyed? before touching.
1130
1289
  MSG
1131
1290
  end
1132
-
1133
- # The name of the method used to touch a +belongs_to+ association when the
1134
- # +:touch+ option is used.
1135
- def belongs_to_touch_method
1136
- :touch
1137
- end
1138
1291
  end
1139
1292
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Promise < BasicObject
5
+ undef_method :==, :!, :!=
6
+
7
+ def initialize(future_result, block) # :nodoc:
8
+ @future_result = future_result
9
+ @block = block
10
+ end
11
+
12
+ # Returns whether the associated query is still being executed or not.
13
+ def pending?
14
+ @future_result.pending?
15
+ end
16
+
17
+ # Returns the query result.
18
+ # If the query wasn't completed yet, accessing +#value+ will block until the query completes.
19
+ # If the query failed, +#value+ will raise the corresponding error.
20
+ def value
21
+ return @value if defined? @value
22
+
23
+ result = @future_result.result
24
+ @value = if @block
25
+ @block.call(result)
26
+ else
27
+ result
28
+ end
29
+ end
30
+
31
+ # Returns a new +ActiveRecord::Promise+ that will apply the passed block
32
+ # when the value is accessed:
33
+ #
34
+ # Post.async_pick(:title).then { |title| title.upcase }.value
35
+ # # => "POST TITLE"
36
+ def then(&block)
37
+ Promise.new(@future_result, @block ? @block >> block : block)
38
+ end
39
+
40
+ [:class, :respond_to?, :is_a?].each do |method|
41
+ define_method(method, ::Object.instance_method(method))
42
+ end
43
+
44
+ def inspect # :nodoc:
45
+ "#<ActiveRecord::Promise status=#{status}>"
46
+ end
47
+
48
+ def pretty_print(q) # :nodoc:
49
+ q.text(inspect)
50
+ end
51
+
52
+ private
53
+ def status
54
+ if @future_result.pending?
55
+ :pending
56
+ elsif @future_result.canceled?
57
+ :canceled
58
+ else
59
+ :complete
60
+ end
61
+ end
62
+
63
+ class Complete < self # :nodoc:
64
+ attr_reader :value
65
+
66
+ def initialize(value)
67
+ @value = value
68
+ end
69
+
70
+ def then
71
+ Complete.new(yield @value)
72
+ end
73
+
74
+ def pending?
75
+ false
76
+ end
77
+
78
+ private
79
+ def status
80
+ :complete
81
+ end
82
+ end
83
+ end
84
+ end
@@ -26,32 +26,14 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def self.run
29
- pools = []
30
-
31
- if ActiveRecord.legacy_connection_handling
32
- ActiveRecord::Base.connection_handlers.each do |key, handler|
33
- pools.concat(handler.connection_pool_list.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
34
- end
35
- else
36
- pools.concat(ActiveRecord::Base.connection_handler.all_connection_pools.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! })
37
- end
38
-
39
- pools
29
+ ActiveRecord::Base.connection_handler.each_connection_pool.reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }
40
30
  end
41
31
 
42
32
  def self.complete(pools)
43
- pools.each { |pool| pool.disable_query_cache! }
33
+ pools.each { |pool| pool.disable_query_cache! unless pool.discarded? }
44
34
 
45
- if ActiveRecord.legacy_connection_handling
46
- ActiveRecord::Base.connection_handlers.each do |_, handler|
47
- handler.connection_pool_list.each do |pool|
48
- pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
49
- end
50
- end
51
- else
52
- ActiveRecord::Base.connection_handler.all_connection_pools.each do |pool|
53
- pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
54
- end
35
+ ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
36
+ pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
55
37
  end
56
38
  end
57
39