activerecord 7.0.8.6 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1340 -1568
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +16 -11
- 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 +12 -9
- 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 +193 -97
- 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 +40 -26
- 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 +109 -32
- 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 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -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 +17 -12
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -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 +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -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 +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- 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 +42 -36
- 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 +162 -77
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -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 +128 -138
- 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 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- 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 +113 -26
- data/lib/active_record/errors.rb +89 -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 +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -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 +100 -4
- 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.rb +213 -109
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +183 -33
- 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 +107 -45
- data/lib/active_record/railties/controller_runtime.rb +10 -5
- data/lib/active_record/railties/databases.rake +139 -145
- 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 +169 -45
- 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 +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- 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/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 +351 -62
- 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 +41 -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 +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +26 -14
- 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 +0 -8
- 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 +52 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -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
|
@@ -183,6 +189,14 @@ module ActiveRecord
|
|
183
189
|
end
|
184
190
|
|
185
191
|
private
|
192
|
+
def init_internals
|
193
|
+
super
|
194
|
+
@mutations_before_last_save = nil
|
195
|
+
@mutations_from_database = nil
|
196
|
+
@_touch_attr_names = nil
|
197
|
+
@_skip_dirty_tracking = nil
|
198
|
+
end
|
199
|
+
|
186
200
|
def _touch_row(attribute_names, time)
|
187
201
|
@_touch_attr_names = Set.new(attribute_names)
|
188
202
|
|
@@ -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
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Write
|
5
6
|
module Write
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -25,9 +26,8 @@ module ActiveRecord
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
# Updates the attribute identified by
|
29
|
-
#
|
30
|
-
# turned into +nil+.
|
29
|
+
# Updates the attribute identified by +attr_name+ using the specified
|
30
|
+
# +value+. The attribute value will be type cast upon being read.
|
31
31
|
def write_attribute(attr_name, value)
|
32
32
|
name = attr_name.to_s
|
33
33
|
name = self.class.attribute_aliases[name] || name
|