activerecord 7.0.8 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1460 -1477
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +15 -9
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +312 -216
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +140 -151
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +131 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +216 -109
- data/lib/active_record/model_schema.rb +64 -44
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +184 -34
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +109 -47
- data/lib/active_record/railties/controller_runtime.rb +10 -5
- data/lib/active_record/railties/databases.rake +142 -148
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +174 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +160 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +352 -63
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +47 -11
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -52,6 +52,23 @@ module ActiveRecord
|
|
52
52
|
attribute_before_type_cast(name)
|
53
53
|
end
|
54
54
|
|
55
|
+
# Returns the value of the attribute identified by +attr_name+ after
|
56
|
+
# serialization.
|
57
|
+
#
|
58
|
+
# class Book < ActiveRecord::Base
|
59
|
+
# enum status: { draft: 1, published: 2 }
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# book = Book.new(status: "published")
|
63
|
+
# book.read_attribute(:status) # => "published"
|
64
|
+
# book.read_attribute_for_database(:status) # => 2
|
65
|
+
def read_attribute_for_database(attr_name)
|
66
|
+
name = attr_name.to_s
|
67
|
+
name = self.class.attribute_aliases[name] || name
|
68
|
+
|
69
|
+
attribute_for_database(name)
|
70
|
+
end
|
71
|
+
|
55
72
|
# Returns a hash of attributes before typecasting and deserialization.
|
56
73
|
#
|
57
74
|
# class Task < ActiveRecord::Base
|
@@ -4,6 +4,38 @@ require "active_support/core_ext/module/attribute_accessors"
|
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module AttributeMethods
|
7
|
+
# = Active Record Attribute Methods \Dirty
|
8
|
+
#
|
9
|
+
# Provides a way to track changes in your Active Record models. It adds all
|
10
|
+
# methods from ActiveModel::Dirty and adds database-specific methods.
|
11
|
+
#
|
12
|
+
# A newly created +Person+ object is unchanged:
|
13
|
+
#
|
14
|
+
# class Person < ActiveRecord::Base
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# person = Person.create(name: "Alisson")
|
18
|
+
# person.changed? # => false
|
19
|
+
#
|
20
|
+
# Change the name:
|
21
|
+
#
|
22
|
+
# person.name = 'Alice'
|
23
|
+
# person.name_in_database # => "Allison"
|
24
|
+
# person.will_save_change_to_name? # => true
|
25
|
+
# person.name_change_to_be_saved # => ["Allison", "Alice"]
|
26
|
+
# person.changes_to_save # => {"name"=>["Allison", "Alice"]}
|
27
|
+
#
|
28
|
+
# Save the changes:
|
29
|
+
#
|
30
|
+
# person.save
|
31
|
+
# person.name_in_database # => "Alice"
|
32
|
+
# person.saved_change_to_name? # => true
|
33
|
+
# person.saved_change_to_name # => ["Allison", "Alice"]
|
34
|
+
# person.name_before_last_change # => "Allison"
|
35
|
+
#
|
36
|
+
# Similar to ActiveModel::Dirty, methods can be invoked as
|
37
|
+
# +saved_change_to_name?+ or by passing an argument to the generic method
|
38
|
+
# <tt>saved_change_to_attribute?("name")</tt>.
|
7
39
|
module Dirty
|
8
40
|
extend ActiveSupport::Concern
|
9
41
|
|
@@ -27,32 +59,6 @@ module ActiveRecord
|
|
27
59
|
attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
|
28
60
|
end
|
29
61
|
|
30
|
-
module ClassMethods
|
31
|
-
def partial_writes
|
32
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
33
|
-
ActiveRecord::Base.partial_writes is deprecated and will be removed in Rails 7.1.
|
34
|
-
Use `partial_updates` and `partial_inserts` instead.
|
35
|
-
MSG
|
36
|
-
partial_updates && partial_inserts
|
37
|
-
end
|
38
|
-
|
39
|
-
def partial_writes?
|
40
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
41
|
-
`ActiveRecord::Base.partial_writes?` is deprecated and will be removed in Rails 7.1.
|
42
|
-
Use `partial_updates?` and `partial_inserts?` instead.
|
43
|
-
MSG
|
44
|
-
partial_updates? && partial_inserts?
|
45
|
-
end
|
46
|
-
|
47
|
-
def partial_writes=(value)
|
48
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
49
|
-
`ActiveRecord::Base.partial_writes=` is deprecated and will be removed in Rails 7.1.
|
50
|
-
Use `partial_updates=` and `partial_inserts=` instead.
|
51
|
-
MSG
|
52
|
-
self.partial_updates = self.partial_inserts = value
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
62
|
# <tt>reload</tt> the record and clears changed attributes.
|
57
63
|
def reload(*)
|
58
64
|
super.tap do
|
@@ -70,11 +76,13 @@ module ActiveRecord
|
|
70
76
|
#
|
71
77
|
# ==== Options
|
72
78
|
#
|
73
|
-
# +from+
|
74
|
-
#
|
79
|
+
# [+from+]
|
80
|
+
# When specified, this method will return false unless the original
|
81
|
+
# value is equal to the given value.
|
75
82
|
#
|
76
|
-
# +to+
|
77
|
-
#
|
83
|
+
# [+to+]
|
84
|
+
# When specified, this method will return false unless the value will be
|
85
|
+
# changed to the given value.
|
78
86
|
def saved_change_to_attribute?(attr_name, **options)
|
79
87
|
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
80
88
|
end
|
@@ -120,11 +128,13 @@ module ActiveRecord
|
|
120
128
|
#
|
121
129
|
# ==== Options
|
122
130
|
#
|
123
|
-
# +from+
|
124
|
-
#
|
131
|
+
# [+from+]
|
132
|
+
# When specified, this method will return false unless the original
|
133
|
+
# value is equal to the given value.
|
125
134
|
#
|
126
|
-
# +to+
|
127
|
-
#
|
135
|
+
# [+to+]
|
136
|
+
# When specified, this method will return false unless the value will be
|
137
|
+
# changed to the given value.
|
128
138
|
def will_save_change_to_attribute?(attr_name, **options)
|
129
139
|
mutations_from_database.changed?(attr_name.to_s, **options)
|
130
140
|
end
|
@@ -183,6 +193,14 @@ module ActiveRecord
|
|
183
193
|
end
|
184
194
|
|
185
195
|
private
|
196
|
+
def init_internals
|
197
|
+
super
|
198
|
+
@mutations_before_last_save = nil
|
199
|
+
@mutations_from_database = nil
|
200
|
+
@_touch_attr_names = nil
|
201
|
+
@_skip_dirty_tracking = nil
|
202
|
+
end
|
203
|
+
|
186
204
|
def _touch_row(attribute_names, time)
|
187
205
|
@_touch_attr_names = Set.new(attribute_names)
|
188
206
|
|
@@ -4,6 +4,7 @@ require "set"
|
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module AttributeMethods
|
7
|
+
# = Active Record Attribute Methods Primary Key
|
7
8
|
module PrimaryKey
|
8
9
|
extend ActiveSupport::Concern
|
9
10
|
|
@@ -11,41 +12,80 @@ module ActiveRecord
|
|
11
12
|
# available.
|
12
13
|
def to_key
|
13
14
|
key = id
|
14
|
-
|
15
|
+
Array(key) if key
|
15
16
|
end
|
16
17
|
|
17
|
-
# Returns the primary key column's value.
|
18
|
+
# Returns the primary key column's value. If the primary key is composite,
|
19
|
+
# returns an array of the primary key column values.
|
18
20
|
def id
|
19
|
-
_read_attribute(@primary_key)
|
21
|
+
return _read_attribute(@primary_key) unless @primary_key.is_a?(Array)
|
22
|
+
|
23
|
+
@primary_key.map { |pk| _read_attribute(pk) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def primary_key_values_present? # :nodoc:
|
27
|
+
return id.all? if self.class.composite_primary_key?
|
28
|
+
|
29
|
+
!!id
|
20
30
|
end
|
21
31
|
|
22
|
-
# Sets the primary key column's value.
|
32
|
+
# Sets the primary key column's value. If the primary key is composite,
|
33
|
+
# raises TypeError when the set value not enumerable.
|
23
34
|
def id=(value)
|
24
|
-
|
35
|
+
if self.class.composite_primary_key?
|
36
|
+
raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable)
|
37
|
+
@primary_key.zip(value) { |attr, value| _write_attribute(attr, value) }
|
38
|
+
else
|
39
|
+
_write_attribute(@primary_key, value)
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
|
-
# Queries the primary key column's value.
|
43
|
+
# Queries the primary key column's value. If the primary key is composite,
|
44
|
+
# all primary key column values must be queryable.
|
28
45
|
def id?
|
29
|
-
|
46
|
+
if self.class.composite_primary_key?
|
47
|
+
@primary_key.all? { |col| _query_attribute(col) }
|
48
|
+
else
|
49
|
+
_query_attribute(@primary_key)
|
50
|
+
end
|
30
51
|
end
|
31
52
|
|
32
|
-
# Returns the primary key column's value before type cast.
|
53
|
+
# Returns the primary key column's value before type cast. If the primary key is composite,
|
54
|
+
# returns an array of primary key column values before type cast.
|
33
55
|
def id_before_type_cast
|
34
|
-
|
56
|
+
if self.class.composite_primary_key?
|
57
|
+
@primary_key.map { |col| attribute_before_type_cast(col) }
|
58
|
+
else
|
59
|
+
attribute_before_type_cast(@primary_key)
|
60
|
+
end
|
35
61
|
end
|
36
62
|
|
37
|
-
# Returns the primary key column's previous value.
|
63
|
+
# Returns the primary key column's previous value. If the primary key is composite,
|
64
|
+
# returns an array of primary key column previous values.
|
38
65
|
def id_was
|
39
|
-
|
66
|
+
if self.class.composite_primary_key?
|
67
|
+
@primary_key.map { |col| attribute_was(col) }
|
68
|
+
else
|
69
|
+
attribute_was(@primary_key)
|
70
|
+
end
|
40
71
|
end
|
41
72
|
|
42
|
-
# Returns the primary key column's value from the database.
|
73
|
+
# Returns the primary key column's value from the database. If the primary key is composite,
|
74
|
+
# returns an array of primary key column values from database.
|
43
75
|
def id_in_database
|
44
|
-
|
76
|
+
if self.class.composite_primary_key?
|
77
|
+
@primary_key.map { |col| attribute_in_database(col) }
|
78
|
+
else
|
79
|
+
attribute_in_database(@primary_key)
|
80
|
+
end
|
45
81
|
end
|
46
82
|
|
47
83
|
def id_for_database # :nodoc:
|
48
|
-
|
84
|
+
if self.class.composite_primary_key?
|
85
|
+
@primary_key.map { |col| @attributes[col].value_for_database }
|
86
|
+
else
|
87
|
+
@attributes[@primary_key].value_for_database
|
88
|
+
end
|
49
89
|
end
|
50
90
|
|
51
91
|
private
|
@@ -55,6 +95,7 @@ module ActiveRecord
|
|
55
95
|
|
56
96
|
module ClassMethods
|
57
97
|
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
|
98
|
+
PRIMARY_KEY_NOT_SET = BasicObject.new
|
58
99
|
|
59
100
|
def instance_method_already_implemented?(method_name)
|
60
101
|
super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
|
@@ -68,10 +109,16 @@ module ActiveRecord
|
|
68
109
|
# Overwriting will negate any effect of the +primary_key_prefix_type+
|
69
110
|
# setting, though.
|
70
111
|
def primary_key
|
71
|
-
|
112
|
+
if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
113
|
+
@primary_key = reset_primary_key
|
114
|
+
end
|
72
115
|
@primary_key
|
73
116
|
end
|
74
117
|
|
118
|
+
def composite_primary_key? # :nodoc:
|
119
|
+
primary_key.is_a?(Array)
|
120
|
+
end
|
121
|
+
|
75
122
|
# Returns a quoted version of the primary key name, used to construct
|
76
123
|
# SQL statements.
|
77
124
|
def quoted_primary_key
|
@@ -93,8 +140,7 @@ module ActiveRecord
|
|
93
140
|
base_name.foreign_key
|
94
141
|
else
|
95
142
|
if ActiveRecord::Base != self && table_exists?
|
96
|
-
|
97
|
-
suppress_composite_primary_key(pk)
|
143
|
+
connection.schema_cache.primary_keys(table_name)
|
98
144
|
else
|
99
145
|
"id"
|
100
146
|
end
|
@@ -117,20 +163,26 @@ module ActiveRecord
|
|
117
163
|
#
|
118
164
|
# Project.primary_key # => "foo_id"
|
119
165
|
def primary_key=(value)
|
120
|
-
@primary_key = value
|
166
|
+
@primary_key = derive_primary_key(value)
|
121
167
|
@quoted_primary_key = nil
|
122
168
|
@attributes_builder = nil
|
123
169
|
end
|
124
170
|
|
125
171
|
private
|
126
|
-
def
|
127
|
-
return
|
172
|
+
def derive_primary_key(value)
|
173
|
+
return unless value
|
174
|
+
|
175
|
+
return -value.to_s unless value.is_a?(Array)
|
128
176
|
|
129
|
-
|
130
|
-
|
177
|
+
value.map { |v| -v.to_s }.freeze
|
178
|
+
end
|
131
179
|
|
132
|
-
|
133
|
-
|
180
|
+
def inherited(base)
|
181
|
+
super
|
182
|
+
base.class_eval do
|
183
|
+
@primary_key = PRIMARY_KEY_NOT_SET
|
184
|
+
@quoted_primary_key = nil
|
185
|
+
end
|
134
186
|
end
|
135
187
|
end
|
136
188
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Query
|
5
6
|
module Query
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -12,27 +13,38 @@ module ActiveRecord
|
|
12
13
|
def query_attribute(attr_name)
|
13
14
|
value = self.public_send(attr_name)
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
query_cast_attribute(attr_name, value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def _query_attribute(attr_name) # :nodoc:
|
20
|
+
value = self._read_attribute(attr_name.to_s)
|
21
|
+
|
22
|
+
query_cast_attribute(attr_name, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias :attribute? :query_attribute
|
26
|
+
private :attribute?
|
27
|
+
|
28
|
+
private
|
29
|
+
def query_cast_attribute(attr_name, value)
|
30
|
+
case value
|
31
|
+
when true then true
|
32
|
+
when false, nil then false
|
33
|
+
else
|
34
|
+
if !type_for_attribute(attr_name) { false }
|
35
|
+
if Numeric === value || !value.match?(/[^0-9]/)
|
36
|
+
!value.to_i.zero?
|
37
|
+
else
|
38
|
+
return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
|
39
|
+
!value.blank?
|
40
|
+
end
|
41
|
+
elsif value.respond_to?(:zero?)
|
42
|
+
!value.zero?
|
22
43
|
else
|
23
|
-
return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
|
24
44
|
!value.blank?
|
25
45
|
end
|
26
|
-
elsif value.respond_to?(:zero?)
|
27
|
-
!value.zero?
|
28
|
-
else
|
29
|
-
!value.blank?
|
30
46
|
end
|
31
47
|
end
|
32
|
-
end
|
33
|
-
|
34
|
-
alias :attribute? :query_attribute
|
35
|
-
private :attribute?
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Read
|
5
6
|
module Read
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -21,15 +22,27 @@ module ActiveRecord
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
# Returns the value of the attribute identified by
|
25
|
-
#
|
26
|
-
# to
|
25
|
+
# Returns the value of the attribute identified by +attr_name+ after it
|
26
|
+
# has been type cast. For example, a date attribute will cast "2004-12-12"
|
27
|
+
# to <tt>Date.new(2004, 12, 12)</tt>. (For information about specific type
|
28
|
+
# casting behavior, see the types under ActiveModel::Type.)
|
27
29
|
def read_attribute(attr_name, &block)
|
28
30
|
name = attr_name.to_s
|
29
31
|
name = self.class.attribute_aliases[name] || name
|
30
32
|
|
31
|
-
name
|
32
|
-
|
33
|
+
return @attributes.fetch_value(name, &block) unless name == "id" && @primary_key
|
34
|
+
|
35
|
+
if self.class.composite_primary_key?
|
36
|
+
@attributes.fetch_value("id", &block)
|
37
|
+
else
|
38
|
+
if @primary_key != "id"
|
39
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
40
|
+
Using read_attribute(:id) to read the primary key value is deprecated.
|
41
|
+
Use #id instead.
|
42
|
+
MSG
|
43
|
+
end
|
44
|
+
@attributes.fetch_value(@primary_key, &block)
|
45
|
+
end
|
33
46
|
end
|
34
47
|
|
35
48
|
# This method exists to avoid the expensive primary_key check internally, without
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Serialization
|
5
6
|
module Serialization
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -15,6 +16,10 @@ module ActiveRecord
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
19
|
+
included do
|
20
|
+
class_attribute :default_column_serializer, instance_accessor: false, default: Coders::YAMLColumn
|
21
|
+
end
|
22
|
+
|
18
23
|
module ClassMethods
|
19
24
|
# If you have an attribute that needs to be saved to the database as a
|
20
25
|
# serialized object, and retrieved by deserializing into the same object,
|
@@ -36,21 +41,19 @@ module ActiveRecord
|
|
36
41
|
# ==== Parameters
|
37
42
|
#
|
38
43
|
# * +attr_name+ - The name of the attribute to serialize.
|
39
|
-
# * +
|
40
|
-
# *
|
41
|
-
# The attribute value must respond to +to_yaml+.
|
42
|
-
# * +Array+ - The attribute value will be serialized as YAML, but an
|
43
|
-
# empty +Array+ will be serialized as +NULL+. The attribute value
|
44
|
-
# must be an +Array+.
|
45
|
-
# * +Hash+ - The attribute value will be serialized as YAML, but an
|
46
|
-
# empty +Hash+ will be serialized as +NULL+. The attribute value
|
47
|
-
# must be a +Hash+.
|
48
|
-
# * +JSON+ - The attribute value will be serialized as JSON. The
|
49
|
-
# attribute value must respond to +to_json+.
|
50
|
-
# * <em>custom coder</em> - The attribute value will be serialized
|
44
|
+
# * +coder+ The serializer implementation to use, e.g. +JSON+.
|
45
|
+
# * The attribute value will be serialized
|
51
46
|
# using the coder's <tt>dump(value)</tt> method, and will be
|
52
47
|
# deserialized using the coder's <tt>load(string)</tt> method. The
|
53
48
|
# +dump+ method may return +nil+ to serialize the value as +NULL+.
|
49
|
+
# * +type+ - Optional. What the type of the serialized object should be.
|
50
|
+
# * Attempting to serialize another type will raise an
|
51
|
+
# ActiveRecord::SerializationTypeMismatch error.
|
52
|
+
# * If the column is +NULL+ or starting from a new record, the default value
|
53
|
+
# will set to +type.new+
|
54
|
+
# * +yaml+ - Optional. Yaml specific options. The allowed config is:
|
55
|
+
# * +:permitted_classes+ - +Array+ with the permitted classes.
|
56
|
+
# * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class.
|
54
57
|
#
|
55
58
|
# ==== Options
|
56
59
|
#
|
@@ -58,24 +61,101 @@ module ActiveRecord
|
|
58
61
|
# this option is not passed, the previous default value (if any) will
|
59
62
|
# be used. Otherwise, the default will be +nil+.
|
60
63
|
#
|
64
|
+
# ==== Choosing a serializer
|
65
|
+
#
|
66
|
+
# While any serialization format can be used, it is recommended to carefully
|
67
|
+
# evaluate the properties of a serializer before using it, as migrating to
|
68
|
+
# another format later on can be difficult.
|
69
|
+
#
|
70
|
+
# ===== Avoid accepting arbitrary types
|
71
|
+
#
|
72
|
+
# When serializing data in a column, it is heavily recommended to make sure
|
73
|
+
# only expected types will be serialized. For instance some serializer like
|
74
|
+
# +Marshal+ or +YAML+ are capable of serializing almost any Ruby object.
|
75
|
+
#
|
76
|
+
# This can lead to unexpected types being serialized, and it is important
|
77
|
+
# that type serialization remains backward and forward compatible as long
|
78
|
+
# as some database records still contain these serialized types.
|
79
|
+
#
|
80
|
+
# class Address
|
81
|
+
# def initialize(line, city, country)
|
82
|
+
# @line, @city, @country = line, city, country
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# In the above example, if any of the +Address+ attributes is renamed,
|
87
|
+
# instances that were persisted before the change will be loaded with the
|
88
|
+
# old attributes. This problem is even worse when the serialized type comes
|
89
|
+
# from a dependency which doesn't expect to be serialized this way and may
|
90
|
+
# change its internal representation without notice.
|
91
|
+
#
|
92
|
+
# As such, it is heavily recommended to instead convert these objects into
|
93
|
+
# primitives of the serialization format, for example:
|
94
|
+
#
|
95
|
+
# class Address
|
96
|
+
# attr_reader :line, :city, :country
|
97
|
+
#
|
98
|
+
# def self.load(payload)
|
99
|
+
# data = YAML.safe_load(payload)
|
100
|
+
# new(data["line"], data["city"], data["country"])
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# def self.dump(address)
|
104
|
+
# YAML.safe_dump(
|
105
|
+
# "line" => address.line,
|
106
|
+
# "city" => address.city,
|
107
|
+
# "country" => address.country,
|
108
|
+
# )
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# def initialize(line, city, country)
|
112
|
+
# @line, @city, @country = line, city, country
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# class User < ActiveRecord::Base
|
117
|
+
# serialize :address, coder: Address
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# This pattern allows to be more deliberate about what is serialized, and
|
121
|
+
# to evolve the format in a backward compatible way.
|
122
|
+
#
|
123
|
+
# ===== Ensure serialization stability
|
124
|
+
#
|
125
|
+
# Some serialization methods may accept some types they don't support by
|
126
|
+
# silently casting them to other types. This can cause bugs when the
|
127
|
+
# data is deserialized.
|
128
|
+
#
|
129
|
+
# For instance the +JSON+ serializer provided in the standard library will
|
130
|
+
# silently cast unsupported types to +String+:
|
131
|
+
#
|
132
|
+
# >> JSON.parse(JSON.dump(Struct.new(:foo)))
|
133
|
+
# => "#<Class:0x000000013090b4c0>"
|
134
|
+
#
|
61
135
|
# ==== Examples
|
62
136
|
#
|
63
137
|
# ===== Serialize the +preferences+ attribute using YAML
|
64
138
|
#
|
65
139
|
# class User < ActiveRecord::Base
|
66
|
-
# serialize :preferences
|
140
|
+
# serialize :preferences, coder: YAML
|
67
141
|
# end
|
68
142
|
#
|
69
143
|
# ===== Serialize the +preferences+ attribute using JSON
|
70
144
|
#
|
71
145
|
# class User < ActiveRecord::Base
|
72
|
-
# serialize :preferences, JSON
|
146
|
+
# serialize :preferences, coder: JSON
|
73
147
|
# end
|
74
148
|
#
|
75
149
|
# ===== Serialize the +preferences+ +Hash+ using YAML
|
76
150
|
#
|
77
151
|
# class User < ActiveRecord::Base
|
78
|
-
# serialize :preferences, Hash
|
152
|
+
# serialize :preferences, type: Hash, coder: YAML
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# ===== Serializes +preferences+ to YAML, permitting select classes
|
156
|
+
#
|
157
|
+
# class User < ActiveRecord::Base
|
158
|
+
# serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] }
|
79
159
|
# end
|
80
160
|
#
|
81
161
|
# ===== Serialize the +preferences+ attribute using a custom coder
|
@@ -97,35 +177,74 @@ module ActiveRecord
|
|
97
177
|
# end
|
98
178
|
#
|
99
179
|
# class User < ActiveRecord::Base
|
100
|
-
# serialize :preferences, Rot13JSON
|
180
|
+
# serialize :preferences, coder: Rot13JSON
|
101
181
|
# end
|
102
182
|
#
|
103
|
-
def serialize(attr_name, class_name_or_coder = Object, **options)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
183
|
+
def serialize(attr_name, class_name_or_coder = nil, coder: nil, type: Object, yaml: {}, **options)
|
184
|
+
unless class_name_or_coder.nil?
|
185
|
+
if class_name_or_coder == ::JSON || [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
186
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
187
|
+
Passing the coder as positional argument is deprecated and will be removed in Rails 7.2.
|
188
|
+
|
189
|
+
Please pass the coder as a keyword argument:
|
190
|
+
|
191
|
+
serialize #{attr_name.inspect}, coder: #{class_name_or_coder}
|
192
|
+
MSG
|
193
|
+
coder = class_name_or_coder
|
194
|
+
else
|
195
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
196
|
+
Passing the class as positional argument is deprecated and will be removed in Rails 7.2.
|
197
|
+
|
198
|
+
Please pass the class as a keyword argument:
|
199
|
+
|
200
|
+
serialize #{attr_name.inspect}, type: #{class_name_or_coder.name}
|
201
|
+
MSG
|
202
|
+
type = class_name_or_coder
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
coder ||= default_column_serializer
|
207
|
+
unless coder
|
208
|
+
raise ArgumentError, <<~MSG.squish
|
209
|
+
missing keyword: :coder
|
210
|
+
|
211
|
+
If no default coder is configured, a coder must be provided to `serialize`.
|
212
|
+
MSG
|
113
213
|
end
|
114
214
|
|
215
|
+
column_serializer = build_column_serializer(attr_name, coder, type, yaml)
|
216
|
+
|
115
217
|
attribute(attr_name, **options) do |cast_type|
|
116
|
-
if type_incompatible_with_serialize?(cast_type,
|
218
|
+
if type_incompatible_with_serialize?(cast_type, coder, type)
|
117
219
|
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
118
220
|
end
|
119
221
|
|
120
222
|
cast_type = cast_type.subtype if Type::Serialized === cast_type
|
121
|
-
Type::Serialized.new(cast_type,
|
223
|
+
Type::Serialized.new(cast_type, column_serializer)
|
122
224
|
end
|
123
225
|
end
|
124
226
|
|
125
227
|
private
|
126
|
-
def
|
127
|
-
|
128
|
-
|
228
|
+
def build_column_serializer(attr_name, coder, type, yaml = nil)
|
229
|
+
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
230
|
+
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
231
|
+
# using the #as_json hook.
|
232
|
+
coder = Coders::JSON if coder == ::JSON
|
233
|
+
|
234
|
+
if coder == ::YAML || coder == Coders::YAMLColumn
|
235
|
+
Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
|
236
|
+
elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
|
237
|
+
coder.new(attr_name, type)
|
238
|
+
elsif type && type != Object
|
239
|
+
Coders::ColumnSerializer.new(attr_name, coder, type)
|
240
|
+
else
|
241
|
+
coder
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def type_incompatible_with_serialize?(cast_type, coder, type)
|
246
|
+
cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
|
247
|
+
cast_type.respond_to?(:type_cast_array, true) && type == ::Array
|
129
248
|
end
|
130
249
|
end
|
131
250
|
end
|