activerecord 7.2.2.2 → 7.2.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +316 -7
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/alias_tracker.rb +6 -4
  5. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  6. data/lib/active_record/associations/collection_association.rb +9 -7
  7. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  8. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  9. data/lib/active_record/attribute_methods.rb +24 -19
  10. data/lib/active_record/attributes.rb +37 -26
  11. data/lib/active_record/autosave_association.rb +22 -12
  12. data/lib/active_record/base.rb +2 -2
  13. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
  14. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
  15. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
  16. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
  18. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
  19. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  20. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
  21. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
  22. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
  23. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  24. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
  26. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  27. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
  29. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  30. data/lib/active_record/connection_handling.rb +12 -8
  31. data/lib/active_record/core.rb +27 -7
  32. data/lib/active_record/counter_cache.rb +1 -1
  33. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  34. data/lib/active_record/delegated_type.rb +18 -18
  35. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  36. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  37. data/lib/active_record/encryption/encryptor.rb +21 -20
  38. data/lib/active_record/enum.rb +13 -12
  39. data/lib/active_record/errors.rb +3 -3
  40. data/lib/active_record/fixture_set/table_row.rb +19 -2
  41. data/lib/active_record/gem_version.rb +2 -2
  42. data/lib/active_record/migration.rb +2 -1
  43. data/lib/active_record/query_logs.rb +4 -0
  44. data/lib/active_record/querying.rb +4 -4
  45. data/lib/active_record/railtie.rb +2 -2
  46. data/lib/active_record/railties/databases.rake +2 -1
  47. data/lib/active_record/relation/calculations.rb +35 -30
  48. data/lib/active_record/relation/finder_methods.rb +10 -10
  49. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  50. data/lib/active_record/relation/query_attribute.rb +1 -1
  51. data/lib/active_record/relation/query_methods.rb +16 -9
  52. data/lib/active_record/relation/where_clause.rb +8 -2
  53. data/lib/active_record/relation.rb +15 -5
  54. data/lib/active_record/schema_dumper.rb +29 -11
  55. data/lib/active_record/secure_token.rb +3 -3
  56. data/lib/active_record/signed_id.rb +7 -6
  57. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
  58. data/lib/active_record/transactions.rb +3 -1
  59. data/lib/active_record.rb +1 -1
  60. data/lib/arel/collectors/bind.rb +1 -1
  61. data/lib/arel/crud.rb +2 -0
  62. data/lib/arel/delete_manager.rb +5 -0
  63. data/lib/arel/nodes/delete_statement.rb +4 -2
  64. data/lib/arel/nodes/update_statement.rb +4 -2
  65. data/lib/arel/select_manager.rb +6 -2
  66. data/lib/arel/update_manager.rb +5 -0
  67. data/lib/arel/visitors/dot.rb +2 -0
  68. data/lib/arel/visitors/to_sql.rb +3 -1
  69. metadata +8 -8
@@ -229,6 +229,8 @@ module ActiveRecord
229
229
  end
230
230
 
231
231
  def defined_for?(name: nil, column: nil, **options)
232
+ options = options.slice(*self.options.keys)
233
+
232
234
  (name.nil? || self.name == name.to_s) &&
233
235
  (column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
234
236
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -302,8 +304,8 @@ module ActiveRecord
302
304
  # t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
303
305
  #
304
306
  # See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
305
- def exclusion_constraint(*args)
306
- @base.add_exclusion_constraint(name, *args)
307
+ def exclusion_constraint(...)
308
+ @base.add_exclusion_constraint(name, ...)
307
309
  end
308
310
 
309
311
  # Removes the given exclusion constraint from the table.
@@ -311,8 +313,8 @@ module ActiveRecord
311
313
  # t.remove_exclusion_constraint(name: "price_check")
312
314
  #
313
315
  # See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
314
- def remove_exclusion_constraint(*args)
315
- @base.remove_exclusion_constraint(name, *args)
316
+ def remove_exclusion_constraint(...)
317
+ @base.remove_exclusion_constraint(name, ...)
316
318
  end
317
319
 
318
320
  # Adds a unique constraint.
@@ -320,8 +322,8 @@ module ActiveRecord
320
322
  # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
321
323
  #
322
324
  # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
323
- def unique_constraint(*args)
324
- @base.add_unique_constraint(name, *args)
325
+ def unique_constraint(...)
326
+ @base.add_unique_constraint(name, ...)
325
327
  end
326
328
 
327
329
  # Removes the given unique constraint from the table.
@@ -329,8 +331,8 @@ module ActiveRecord
329
331
  # t.remove_unique_constraint(name: "unique_position")
330
332
  #
331
333
  # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
332
- def remove_unique_constraint(*args)
333
- @base.remove_unique_constraint(name, *args)
334
+ def remove_unique_constraint(...)
335
+ @base.remove_unique_constraint(name, ...)
334
336
  end
335
337
 
336
338
  # Validates the given constraint on the table.
@@ -339,8 +341,8 @@ module ActiveRecord
339
341
  # t.validate_constraint "price_check"
340
342
  #
341
343
  # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
342
- def validate_constraint(*args)
343
- @base.validate_constraint(name, *args)
344
+ def validate_constraint(...)
345
+ @base.validate_constraint(name, ...)
344
346
  end
345
347
 
346
348
  # Validates the given check constraint on the table
@@ -349,8 +351,8 @@ module ActiveRecord
349
351
  # t.validate_check_constraint name: "price_check"
350
352
  #
351
353
  # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
352
- def validate_check_constraint(*args)
353
- @base.validate_check_constraint(name, *args)
354
+ def validate_check_constraint(...)
355
+ @base.validate_check_constraint(name, ...)
354
356
  end
355
357
  end
356
358
 
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  stream.puts " # Custom types defined in this database."
23
23
  stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
24
  types.sort.each do |name, values|
25
- stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
25
+ stream.puts " create_enum #{name.inspect}, #{values.inspect}"
26
26
  end
27
27
  stream.puts
28
28
  end
@@ -879,7 +879,7 @@ module ActiveRecord
879
879
  #
880
880
  # validate_check_constraint :products, name: "price_check"
881
881
  #
882
- # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
882
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
883
883
  def validate_check_constraint(table_name, **options)
884
884
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
885
885
 
@@ -25,7 +25,7 @@ module ActiveRecord
25
25
  #
26
26
  # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
27
27
  #
28
- # Options:
28
+ # ==== Options
29
29
  #
30
30
  # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
31
31
  # the default is to connect to localhost.
@@ -345,6 +345,7 @@ module ActiveRecord
345
345
  @lock.synchronize do
346
346
  return false unless @raw_connection
347
347
  @raw_connection.query ";"
348
+ verified!
348
349
  end
349
350
  true
350
351
  rescue PG::Error
@@ -503,7 +504,7 @@ module ActiveRecord
503
504
  type.typname AS name,
504
505
  type.OID AS oid,
505
506
  n.nspname AS schema,
506
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
507
+ array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value
507
508
  FROM pg_enum AS enum
508
509
  JOIN pg_type AS type ON (type.oid = enum.enumtypid)
509
510
  JOIN pg_namespace n ON type.typnamespace = n.oid
@@ -612,7 +613,11 @@ module ActiveRecord
612
613
  # Returns the version of the connected PostgreSQL server.
613
614
  def get_database_version # :nodoc:
614
615
  with_raw_connection do |conn|
615
- conn.server_version
616
+ version = conn.server_version
617
+ if version == 0
618
+ raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version"
619
+ end
620
+ version
616
621
  end
617
622
  end
618
623
  alias :postgresql_version :database_version
@@ -50,6 +50,19 @@ module ActiveRecord
50
50
  end
51
51
  end
52
52
 
53
+ def quote(value) # :nodoc:
54
+ case value
55
+ when Numeric
56
+ if value.finite?
57
+ super
58
+ else
59
+ "'#{value}'"
60
+ end
61
+ else
62
+ super
63
+ end
64
+ end
65
+
53
66
  def quote_string(s)
54
67
  ::SQLite3::Database.quote(s)
55
68
  end
@@ -74,6 +74,7 @@ module ActiveRecord
74
74
  Base.pluralize_table_names ? table.pluralize : table
75
75
  end
76
76
  table = strip_table_name_prefix_and_suffix(table)
77
+ options = options.slice(*fk.options.keys)
77
78
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
78
79
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
79
80
  end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  # The SQLite3 adapter works with the sqlite3-ruby drivers
22
22
  # (available as gem from https://rubygems.org/gems/sqlite3).
23
23
  #
24
- # Options:
24
+ # ==== Options
25
25
  #
26
26
  # * <tt>:database</tt> - Path to the database file.
27
27
  class SQLite3Adapter < AbstractAdapter
@@ -199,7 +199,12 @@ module ActiveRecord
199
199
  !(@raw_connection.nil? || @raw_connection.closed?)
200
200
  end
201
201
 
202
- alias_method :active?, :connected?
202
+ def active?
203
+ if connected?
204
+ verified!
205
+ true
206
+ end
207
+ end
203
208
 
204
209
  alias :reset! :reconnect!
205
210
 
@@ -367,7 +372,7 @@ module ActiveRecord
367
372
  end
368
373
  alias :add_belongs_to :add_reference
369
374
 
370
- FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
375
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
371
376
  DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
372
377
  def foreign_keys(table_name)
373
378
  # SQLite returns 1 row for each column of composite foreign keys.
@@ -542,8 +547,8 @@ module ActiveRecord
542
547
  yield definition if block_given?
543
548
  end
544
549
 
545
- transaction do
546
- disable_referential_integrity do
550
+ disable_referential_integrity do
551
+ transaction do
547
552
  move_table(table_name, altered_table_name, options.merge(temporary: true))
548
553
  move_table(altered_table_name, table_name, &caller)
549
554
  end
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  end
122
122
 
123
123
  def active?
124
- connected? && @lock.synchronize { @raw_connection&.ping } || false
124
+ connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false
125
125
  rescue ::Trilogy::Error
126
126
  false
127
127
  end
@@ -170,9 +170,11 @@ module ActiveRecord
170
170
  prevent_writes = true if role == ActiveRecord.reading_role
171
171
 
172
172
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
173
- yield
174
- ensure
175
- connected_to_stack.pop
173
+ begin
174
+ yield
175
+ ensure
176
+ connected_to_stack.pop
177
+ end
176
178
  end
177
179
 
178
180
  # Use a specified connection.
@@ -373,11 +375,13 @@ module ActiveRecord
373
375
  prevent_writes = true if role == ActiveRecord.reading_role
374
376
 
375
377
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
376
- return_value = yield
377
- return_value.load if return_value.is_a? ActiveRecord::Relation
378
- return_value
379
- ensure
380
- self.connected_to_stack.pop
378
+ begin
379
+ return_value = yield
380
+ return_value.load if return_value.is_a? ActiveRecord::Relation
381
+ return_value
382
+ ensure
383
+ self.connected_to_stack.pop
384
+ end
381
385
  end
382
386
 
383
387
  def append_to_connected_to_stack(entry)
@@ -585,7 +585,7 @@ module ActiveRecord
585
585
  #
586
586
  # topic = Topic.new(title: "Budget", author_name: "Jason")
587
587
  # topic.slice(:title, :author_name)
588
- # => { "title" => "Budget", "author_name" => "Jason" }
588
+ # # => { "title" => "Budget", "author_name" => "Jason" }
589
589
  #
590
590
  #--
591
591
  # Implemented by ActiveModel::Access#slice.
@@ -599,7 +599,7 @@ module ActiveRecord
599
599
  #
600
600
  # topic = Topic.new(title: "Budget", author_name: "Jason")
601
601
  # topic.values_at(:title, :author_name)
602
- # => ["Budget", "Jason"]
602
+ # # => ["Budget", "Jason"]
603
603
  #
604
604
  #--
605
605
  # Implemented by ActiveModel::Access#values_at.
@@ -676,12 +676,14 @@ module ActiveRecord
676
676
  # Sets the record to strict_loading mode. This will raise an error
677
677
  # if the record tries to lazily load an association.
678
678
  #
679
+ # NOTE: Strict loading is disabled during validation in order to let the record validate its association.
680
+ #
679
681
  # user = User.first
680
682
  # user.strict_loading! # => true
681
683
  # user.address.city
682
- # => ActiveRecord::StrictLoadingViolationError
684
+ # # => ActiveRecord::StrictLoadingViolationError
683
685
  # user.comments.to_a
684
- # => ActiveRecord::StrictLoadingViolationError
686
+ # # => ActiveRecord::StrictLoadingViolationError
685
687
  #
686
688
  # ==== Parameters
687
689
  #
@@ -701,7 +703,7 @@ module ActiveRecord
701
703
  # user.address.city # => "Tatooine"
702
704
  # user.comments.to_a # => [#<Comment:0x00...]
703
705
  # user.comments.first.ratings.to_a
704
- # => ActiveRecord::StrictLoadingViolationError
706
+ # # => ActiveRecord::StrictLoadingViolationError
705
707
  def strict_loading!(value = true, mode: :all)
706
708
  unless [:all, :n_plus_one_only].include?(mode)
707
709
  raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
@@ -723,11 +725,29 @@ module ActiveRecord
723
725
  @strict_loading_mode == :all
724
726
  end
725
727
 
726
- # Marks this record as read only.
728
+ # Prevents records from being written to the database:
729
+ #
730
+ # customer = Customer.new
731
+ # customer.readonly!
732
+ # customer.save # raises ActiveRecord::ReadOnlyRecord
727
733
  #
728
734
  # customer = Customer.first
729
735
  # customer.readonly!
730
- # customer.save # Raises an ActiveRecord::ReadOnlyRecord
736
+ # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
737
+ #
738
+ # Read-only records cannot be deleted from the database either:
739
+ #
740
+ # customer = Customer.first
741
+ # customer.readonly!
742
+ # customer.destroy # raises ActiveRecord::ReadOnlyRecord
743
+ #
744
+ # Please, note that the objects themselves are still mutable in memory:
745
+ #
746
+ # customer = Customer.new
747
+ # customer.readonly!
748
+ # customer.name = 'New Name' # OK
749
+ #
750
+ # but you won't be able to persist the changes.
731
751
  def readonly!
732
752
  @readonly = true
733
753
  end
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  # # For the Post with id #1, reset the comments_count
29
29
  # Post.reset_counters(1, :comments)
30
30
  #
31
- # # Like above, but also touch the +updated_at+ and/or +updated_on+
31
+ # # Like above, but also touch the updated_at and/or updated_on
32
32
  # # attributes.
33
33
  # Post.reset_counters(1, :comments, touch: true)
34
34
  def reset_counters(id, *counters, touch: nil)
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
 
82
82
  def resolved_adapter
83
83
  adapter = uri.scheme && @uri.scheme.tr("-", "_")
84
- adapter = ActiveRecord.protocol_adapters[adapter] || adapter
84
+ if adapter && ActiveRecord.protocol_adapters[adapter]
85
+ adapter = ActiveRecord.protocol_adapters[adapter]
86
+ end
85
87
  adapter
86
88
  end
87
89
 
@@ -181,16 +181,16 @@ module ActiveRecord
181
181
  # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
182
182
  # end
183
183
  #
184
- # Entry#entryable_class # => +Message+ or +Comment+
185
- # Entry#entryable_name # => "message" or "comment"
186
- # Entry.messages # => Entry.where(entryable_type: "Message")
187
- # Entry#message? # => true when entryable_type == "Message"
188
- # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
189
- # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
190
- # Entry.comments # => Entry.where(entryable_type: "Comment")
191
- # Entry#comment? # => true when entryable_type == "Comment"
192
- # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
193
- # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
184
+ # @entry.entryable_class # => Message or Comment
185
+ # @entry.entryable_name # => "message" or "comment"
186
+ # Entry.messages # => Entry.where(entryable_type: "Message")
187
+ # @entry.message? # => true when entryable_type == "Message"
188
+ # @entry.message # => returns the message record, when entryable_type == "Message", otherwise nil
189
+ # @entry.message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
190
+ # Entry.comments # => Entry.where(entryable_type: "Comment")
191
+ # @entry.comment? # => true when entryable_type == "Comment"
192
+ # @entry.comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
193
+ # @entry.comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
194
194
  #
195
195
  # You can also declare namespaced types:
196
196
  #
@@ -199,25 +199,25 @@ module ActiveRecord
199
199
  # end
200
200
  #
201
201
  # Entry.access_notice_messages
202
- # entry.access_notice_message
203
- # entry.access_notice_message?
202
+ # @entry.access_notice_message
203
+ # @entry.access_notice_message?
204
204
  #
205
- # === Options
205
+ # ==== Options
206
206
  #
207
207
  # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
208
208
  # The following options can be included to specialize the behavior of the delegated type convenience methods.
209
209
  #
210
- # [:foreign_key]
210
+ # [+:foreign_key+]
211
211
  # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
212
212
  # +role+ with an "_id" suffix. So a class that defines a
213
213
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
214
214
  # the default <tt>:foreign_key</tt>.
215
- # [:foreign_type]
215
+ # [+:foreign_type+]
216
216
  # Specify the column used to store the associated object's type. By default this is inferred to be the passed
217
217
  # +role+ with a "_type" suffix. A class that defines a
218
218
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
219
219
  # the default <tt>:foreign_type</tt>.
220
- # [:primary_key]
220
+ # [+:primary_key+]
221
221
  # Specify the method that returns the primary key of associated object used for the convenience methods.
222
222
  # By default this is +id+.
223
223
  #
@@ -226,8 +226,8 @@ module ActiveRecord
226
226
  # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
227
227
  # end
228
228
  #
229
- # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
230
- # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
229
+ # @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
230
+ # @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
231
231
  def delegated_type(role, types:, **options)
232
232
  belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
233
233
  define_delegated_type_methods role, types: types, options: options
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  class_methods do
17
17
  # Encrypts the +name+ attribute.
18
18
  #
19
- # === Options
19
+ # ==== Options
20
20
  #
21
21
  # * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
22
22
  # +ActiveRecord::Encryption.key_provider+.
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
16
16
  delegate :accessor, :type, to: :cast_type
17
17
 
18
- # === Options
18
+ # ==== Options
19
19
  #
20
20
  # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
21
21
  # * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
@@ -12,33 +12,34 @@ module ActiveRecord
12
12
  # It interacts with a KeyProvider for getting the keys, and delegate to
13
13
  # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
14
  class Encryptor
15
- # === Options
15
+ # ==== Options
16
16
  #
17
- # * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
18
- # Defaults to +true+.
17
+ # [+:compress+]
18
+ # Boolean indicating whether records should be compressed before
19
+ # encryption. Defaults to +true+.
19
20
  def initialize(compress: true)
20
21
  @compress = compress
21
22
  end
22
23
 
23
- # Encrypts +clean_text+ and returns the encrypted result
24
+ # Encrypts +clean_text+ and returns the encrypted result.
24
25
  #
25
26
  # Internally, it will:
26
27
  #
27
- # 1. Create a new ActiveRecord::Encryption::Message
28
- # 2. Compress and encrypt +clean_text+ as the message payload
29
- # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
30
- # by default)
31
- # 4. Encode the result with Base 64
28
+ # 1. Create a new ActiveRecord::Encryption::Message.
29
+ # 2. Compress and encrypt +clean_text+ as the message payload.
30
+ # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
31
+ # (+ActiveRecord::Encryption::SafeMarshal+ by default).
32
+ # 4. Encode the result with Base64.
32
33
  #
33
- # === Options
34
+ # ==== Options
34
35
  #
35
- # [:key_provider]
36
+ # [+:key_provider+]
36
37
  # Key provider to use for the encryption operation. It will default to
37
38
  # +ActiveRecord::Encryption.key_provider+ when not provided.
38
39
  #
39
- # [:cipher_options]
40
+ # [+:cipher_options+]
40
41
  # Cipher-specific options that will be passed to the Cipher configured in
41
- # +ActiveRecord::Encryption.cipher+
42
+ # +ActiveRecord::Encryption.cipher+.
42
43
  def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
43
44
  clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
44
45
 
@@ -46,17 +47,17 @@ module ActiveRecord
46
47
  serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
47
48
  end
48
49
 
49
- # Decrypts an +encrypted_text+ and returns the result as clean text
50
+ # Decrypts an +encrypted_text+ and returns the result as clean text.
50
51
  #
51
- # === Options
52
+ # ==== Options
52
53
  #
53
- # [:key_provider]
54
+ # [+:key_provider+]
54
55
  # Key provider to use for the encryption operation. It will default to
55
- # +ActiveRecord::Encryption.key_provider+ when not provided
56
+ # +ActiveRecord::Encryption.key_provider+ when not provided.
56
57
  #
57
- # [:cipher_options]
58
+ # [+:cipher_options+]
58
59
  # Cipher-specific options that will be passed to the Cipher configured in
59
- # +ActiveRecord::Encryption.cipher+
60
+ # +ActiveRecord::Encryption.cipher+.
60
61
  def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
61
62
  message = deserialize_message(encrypted_text)
62
63
  keys = key_provider.decryption_keys(message)
@@ -66,7 +67,7 @@ module ActiveRecord
66
67
  raise Errors::Decryption
67
68
  end
68
69
 
69
- # Returns whether the text is encrypted or not
70
+ # Returns whether the text is encrypted or not.
70
71
  def encrypted?(text)
71
72
  deserialize_message(text)
72
73
  true
@@ -119,7 +119,18 @@ module ActiveRecord
119
119
  # enum :status, [ :active, :archived ], instance_methods: false
120
120
  # end
121
121
  #
122
- # If you want the enum value to be validated before saving, use the option +:validate+:
122
+ # By default, an +ArgumentError+ will be raised when assigning an invalid value:
123
+ #
124
+ # class Conversation < ActiveRecord::Base
125
+ # enum :status, [ :active, :archived ]
126
+ # end
127
+ #
128
+ # conversation = Conversation.new
129
+ #
130
+ # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
131
+ #
132
+ # If, instead, you want the enum value to be validated before saving, use the
133
+ # +:validate+ option:
123
134
  #
124
135
  # class Conversation < ActiveRecord::Base
125
136
  # enum :status, [ :active, :archived ], validate: true
@@ -136,7 +147,7 @@ module ActiveRecord
136
147
  # conversation.status = :active
137
148
  # conversation.valid? # => true
138
149
  #
139
- # It is also possible to pass additional validation options:
150
+ # You may also pass additional validation options:
140
151
  #
141
152
  # class Conversation < ActiveRecord::Base
142
153
  # enum :status, [ :active, :archived ], validate: { allow_nil: true }
@@ -152,16 +163,6 @@ module ActiveRecord
152
163
  #
153
164
  # conversation.status = :active
154
165
  # conversation.valid? # => true
155
- #
156
- # Otherwise +ArgumentError+ will raise:
157
- #
158
- # class Conversation < ActiveRecord::Base
159
- # enum :status, [ :active, :archived ]
160
- # end
161
- #
162
- # conversation = Conversation.new
163
- #
164
- # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
165
166
  module Enum
166
167
  def self.extended(base) # :nodoc:
167
168
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
 
14
14
  # Raised when the single-table inheritance mechanism fails to locate the subclass
15
15
  # (for example due to improper usage of column that
16
- # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
16
+ # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema.inheritance_column]
17
17
  # points to).
18
18
  class SubclassNotFound < ActiveRecordError
19
19
  end
@@ -431,7 +431,7 @@ module ActiveRecord
431
431
  UnknownAttributeError = ActiveModel::UnknownAttributeError
432
432
 
433
433
  # Raised when an error occurred while doing a mass assignment to an attribute through the
434
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
434
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
435
435
  # The exception has an +attribute+ property that is the name of the offending attribute.
436
436
  class AttributeAssignmentError < ActiveRecordError
437
437
  attr_reader :exception, :attribute
@@ -444,7 +444,7 @@ module ActiveRecord
444
444
  end
445
445
 
446
446
  # Raised when there are multiple errors while doing a mass assignment through the
447
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
447
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=]
448
448
  # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
449
449
  # objects, each corresponding to the error while assigning to an attribute.
450
450
  class MultiparameterAssignmentErrors < ActiveRecordError
@@ -193,8 +193,25 @@ module ActiveRecord
193
193
 
194
194
  targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
195
195
  joins = targets.map do |target|
196
- join = { lhs_key => @row[model_metadata.primary_key_name],
197
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
196
+ join = {}
197
+
198
+ if rhs_key.is_a?(Array)
199
+ composite_key = ActiveRecord::FixtureSet.composite_identify(target, rhs_key)
200
+ composite_key.each do |column, value|
201
+ join[column] = value
202
+ end
203
+ else
204
+ join[rhs_key] = ActiveRecord::FixtureSet.identify(target, column_type)
205
+ end
206
+
207
+ if lhs_key.is_a?(Array)
208
+ lhs_key.zip(model_metadata.primary_key_name).each do |fkey, pkey|
209
+ join[fkey] = @row[pkey]
210
+ end
211
+ else
212
+ join[lhs_key] = @row[model_metadata.primary_key_name]
213
+ end
214
+
198
215
  association.timestamp_column_names.each do |col|
199
216
  join[col] = @now
200
217
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 2
12
- TINY = 2
13
- PRE = "2"
12
+ TINY = 3
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -1532,7 +1532,8 @@ module ActiveRecord
1532
1532
  return if down? && !migrated.include?(migration.version.to_i)
1533
1533
  return if up? && migrated.include?(migration.version.to_i)
1534
1534
 
1535
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
1535
+ message = up? ? "Migrating to" : "Reverting"
1536
+ Base.logger.info "#{message} #{migration.name} (#{migration.version})" if Base.logger
1536
1537
 
1537
1538
  ddl_transaction(migration) do
1538
1539
  migration.migrate(@direction)
@@ -28,6 +28,10 @@ module ActiveRecord
28
28
  # * +database+
29
29
  # * +source_location+
30
30
  #
31
+ # WARNING: Calculating the +source_location+ of a query can be slow, so you should consider its impact if using it in a production environment.
32
+ #
33
+ # Also see {config.active_record.verbose_query_logs}[https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs].
34
+ #
31
35
  # Action Controller adds default tags when loaded:
32
36
  #
33
37
  # * +controller+