activerecord 4.2.11.1 → 5.2.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +579 -1635
- data/MIT-LICENSE +2 -2
- data/README.rdoc +10 -11
- data/examples/performance.rb +32 -31
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +263 -249
- data/lib/active_record/association_relation.rb +11 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +77 -43
- data/lib/active_record/associations/association_scope.rb +106 -133
- data/lib/active_record/associations/belongs_to_association.rb +52 -41
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
- data/lib/active_record/associations/builder/association.rb +29 -38
- data/lib/active_record/associations/builder/belongs_to.rb +77 -30
- data/lib/active_record/associations/builder/collection_association.rb +9 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
- data/lib/active_record/associations/builder/has_many.rb +6 -4
- data/lib/active_record/associations/builder/has_one.rb +13 -6
- data/lib/active_record/associations/builder/singular_association.rb +15 -11
- data/lib/active_record/associations/collection_association.rb +139 -280
- data/lib/active_record/associations/collection_proxy.rb +231 -133
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_association.rb +34 -89
- data/lib/active_record/associations/has_many_through_association.rb +49 -76
- data/lib/active_record/associations/has_one_association.rb +38 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -87
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/join_dependency.rb +133 -159
- data/lib/active_record/associations/preloader/association.rb +85 -120
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +81 -91
- data/lib/active_record/associations/singular_association.rb +27 -34
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/associations.rb +1732 -1597
- data/lib/active_record/attribute_assignment.rb +58 -182
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
- data/lib/active_record/attribute_methods/dirty.rb +94 -135
- data/lib/active_record/attribute_methods/primary_key.rb +86 -71
- data/lib/active_record/attribute_methods/query.rb +4 -2
- data/lib/active_record/attribute_methods/read.rb +45 -63
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
- data/lib/active_record/attribute_methods/write.rb +30 -45
- data/lib/active_record/attribute_methods.rb +166 -109
- data/lib/active_record/attributes.rb +201 -82
- data/lib/active_record/autosave_association.rb +94 -36
- data/lib/active_record/base.rb +57 -44
- data/lib/active_record/callbacks.rb +97 -57
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +24 -12
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
- data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
- data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
- data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
- data/lib/active_record/connection_adapters/column.rb +50 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -180
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -7
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +40 -27
- data/lib/active_record/core.rb +178 -198
- data/lib/active_record/counter_cache.rb +79 -36
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +135 -88
- data/lib/active_record/errors.rb +179 -52
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +188 -132
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +148 -112
- data/lib/active_record/integration.rb +70 -28
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +88 -96
- data/lib/active_record/locking/pessimistic.rb +15 -3
- data/lib/active_record/log_subscriber.rb +95 -33
- data/lib/active_record/migration/command_recorder.rb +133 -90
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/migration.rb +581 -282
- data/lib/active_record/model_schema.rb +290 -111
- data/lib/active_record/nested_attributes.rb +264 -222
- data/lib/active_record/no_touching.rb +7 -1
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +347 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +94 -32
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +9 -3
- data/lib/active_record/railties/databases.rake +149 -156
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +414 -267
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +204 -55
- data/lib/active_record/relation/calculations.rb +256 -248
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +288 -239
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +86 -86
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +116 -119
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +448 -393
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -13
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +287 -340
- data/lib/active_record/result.rb +54 -36
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +155 -124
- data/lib/active_record/schema.rb +30 -24
- data/lib/active_record/schema_dumper.rb +91 -87
- data/lib/active_record/schema_migration.rb +19 -16
- data/lib/active_record/scoping/default.rb +102 -85
- data/lib/active_record/scoping/named.rb +81 -32
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +45 -35
- data/lib/active_record/store.rb +42 -36
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +134 -96
- data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +199 -124
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +24 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type.rb +79 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +40 -41
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +34 -22
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
- data/lib/rails/generators/active_record/migration.rb +18 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +72 -49
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
@@ -1,33 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute/user_provided_default"
|
4
|
+
|
1
5
|
module ActiveRecord
|
2
|
-
|
6
|
+
# See ActiveRecord::Attributes::ClassMethods for documentation
|
7
|
+
module Attributes
|
3
8
|
extend ActiveSupport::Concern
|
4
9
|
|
5
|
-
Type = ActiveRecord::Type
|
6
|
-
|
7
10
|
included do
|
8
|
-
class_attribute :
|
9
|
-
class_attribute :user_provided_defaults, instance_accessor: false # :internal:
|
10
|
-
self.user_provided_columns = {}
|
11
|
-
self.user_provided_defaults = {}
|
12
|
-
|
13
|
-
delegate :persistable_attribute_names, to: :class
|
11
|
+
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
|
14
12
|
end
|
15
13
|
|
16
|
-
module ClassMethods
|
17
|
-
# Defines
|
18
|
-
#
|
19
|
-
#
|
14
|
+
module ClassMethods
|
15
|
+
# Defines an attribute with a type on this model. It will override the
|
16
|
+
# type of existing attributes if needed. This allows control over how
|
17
|
+
# values are converted to and from SQL when assigned to a model. It also
|
18
|
+
# changes the behavior of values passed to
|
19
|
+
# {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
|
20
|
+
# your domain objects across much of Active Record, without having to
|
21
|
+
# rely on implementation details or monkey patching.
|
20
22
|
#
|
21
|
-
# +name+ The name of the methods to define attribute methods for, and the
|
22
|
-
# this will persist to.
|
23
|
+
# +name+ The name of the methods to define attribute methods for, and the
|
24
|
+
# column which this will persist to.
|
23
25
|
#
|
24
|
-
# +cast_type+ A
|
25
|
-
# See the examples
|
26
|
+
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
|
27
|
+
# to be used for this attribute. See the examples below for more
|
28
|
+
# information about providing custom type objects.
|
26
29
|
#
|
27
30
|
# ==== Options
|
28
|
-
# The options hash accepts the following options:
|
29
31
|
#
|
30
|
-
#
|
32
|
+
# The following options are accepted:
|
33
|
+
#
|
34
|
+
# +default+ The default value to use when no value is provided. If this option
|
35
|
+
# is not passed, the previous default value (if any) will be used.
|
36
|
+
# Otherwise, the default will be +nil+.
|
37
|
+
#
|
38
|
+
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
|
39
|
+
# examples below).
|
40
|
+
#
|
41
|
+
# +range+ (PostgreSQL only) specifies that the type should be a range (see the
|
42
|
+
# examples below).
|
31
43
|
#
|
32
44
|
# ==== Examples
|
33
45
|
#
|
@@ -45,103 +57,210 @@ module ActiveRecord
|
|
45
57
|
# store_listing = StoreListing.new(price_in_cents: '10.1')
|
46
58
|
#
|
47
59
|
# # before
|
48
|
-
# store_listing.price_in_cents # => BigDecimal
|
60
|
+
# store_listing.price_in_cents # => BigDecimal(10.1)
|
49
61
|
#
|
50
62
|
# class StoreListing < ActiveRecord::Base
|
51
|
-
# attribute :price_in_cents,
|
63
|
+
# attribute :price_in_cents, :integer
|
52
64
|
# end
|
53
65
|
#
|
54
66
|
# # after
|
55
67
|
# store_listing.price_in_cents # => 10
|
56
68
|
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
69
|
+
# A default can also be provided.
|
70
|
+
#
|
71
|
+
# # db/schema.rb
|
72
|
+
# create_table :store_listings, force: true do |t|
|
73
|
+
# t.string :my_string, default: "original default"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# StoreListing.new.my_string # => "original default"
|
77
|
+
#
|
78
|
+
# # app/models/store_listing.rb
|
79
|
+
# class StoreListing < ActiveRecord::Base
|
80
|
+
# attribute :my_string, :string, default: "new default"
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# StoreListing.new.my_string # => "new default"
|
84
|
+
#
|
85
|
+
# class Product < ActiveRecord::Base
|
86
|
+
# attribute :my_default_proc, :datetime, default: -> { Time.now }
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
|
90
|
+
# sleep 1
|
91
|
+
# Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
|
92
|
+
#
|
93
|
+
# \Attributes do not need to be backed by a database column.
|
94
|
+
#
|
95
|
+
# # app/models/my_model.rb
|
96
|
+
# class MyModel < ActiveRecord::Base
|
97
|
+
# attribute :my_string, :string
|
98
|
+
# attribute :my_int_array, :integer, array: true
|
99
|
+
# attribute :my_float_range, :float, range: true
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# model = MyModel.new(
|
103
|
+
# my_string: "string",
|
104
|
+
# my_int_array: ["1", "2", "3"],
|
105
|
+
# my_float_range: "[1,3.5]",
|
106
|
+
# )
|
107
|
+
# model.attributes
|
108
|
+
# # =>
|
109
|
+
# {
|
110
|
+
# my_string: "string",
|
111
|
+
# my_int_array: [1, 2, 3],
|
112
|
+
# my_float_range: 1.0..3.5
|
113
|
+
# }
|
114
|
+
#
|
115
|
+
# ==== Creating Custom Types
|
116
|
+
#
|
117
|
+
# Users may also define their own custom types, as long as they respond
|
118
|
+
# to the methods defined on the value type. The method +deserialize+ or
|
119
|
+
# +cast+ will be called on your type object, with raw input from the
|
120
|
+
# database or from your controllers. See ActiveModel::Type::Value for the
|
121
|
+
# expected API. It is recommended that your type objects inherit from an
|
122
|
+
# existing type, or from ActiveRecord::Type::Value
|
62
123
|
#
|
63
124
|
# class MoneyType < ActiveRecord::Type::Integer
|
64
|
-
# def
|
65
|
-
# if value.include?('$')
|
125
|
+
# def cast(value)
|
126
|
+
# if !value.kind_of?(Numeric) && value.include?('$')
|
66
127
|
# price_in_dollars = value.gsub(/\$/, '').to_f
|
67
|
-
# price_in_dollars * 100
|
128
|
+
# super(price_in_dollars * 100)
|
68
129
|
# else
|
69
|
-
#
|
130
|
+
# super
|
70
131
|
# end
|
71
132
|
# end
|
72
133
|
# end
|
73
134
|
#
|
135
|
+
# # config/initializers/types.rb
|
136
|
+
# ActiveRecord::Type.register(:money, MoneyType)
|
137
|
+
#
|
138
|
+
# # app/models/store_listing.rb
|
74
139
|
# class StoreListing < ActiveRecord::Base
|
75
|
-
# attribute :price_in_cents,
|
140
|
+
# attribute :price_in_cents, :money
|
76
141
|
# end
|
77
142
|
#
|
78
143
|
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
79
144
|
# store_listing.price_in_cents # => 1000
|
80
|
-
|
145
|
+
#
|
146
|
+
# For more details on creating custom types, see the documentation for
|
147
|
+
# ActiveModel::Type::Value. For more details on registering your types
|
148
|
+
# to be referenced by a symbol, see ActiveRecord::Type.register. You can
|
149
|
+
# also pass a type object directly, in place of a symbol.
|
150
|
+
#
|
151
|
+
# ==== \Querying
|
152
|
+
#
|
153
|
+
# When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
|
154
|
+
# use the type defined by the model class to convert the value to SQL,
|
155
|
+
# calling +serialize+ on your type object. For example:
|
156
|
+
#
|
157
|
+
# class Money < Struct.new(:amount, :currency)
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# class MoneyType < Type::Value
|
161
|
+
# def initialize(currency_converter:)
|
162
|
+
# @currency_converter = currency_converter
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# # value will be the result of +deserialize+ or
|
166
|
+
# # +cast+. Assumed to be an instance of +Money+ in
|
167
|
+
# # this case.
|
168
|
+
# def serialize(value)
|
169
|
+
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
170
|
+
# value_in_bitcoins.amount
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# # config/initializers/types.rb
|
175
|
+
# ActiveRecord::Type.register(:money, MoneyType)
|
176
|
+
#
|
177
|
+
# # app/models/product.rb
|
178
|
+
# class Product < ActiveRecord::Base
|
179
|
+
# currency_converter = ConversionRatesFromTheInternet.new
|
180
|
+
# attribute :price_in_bitcoins, :money, currency_converter: currency_converter
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
184
|
+
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
|
185
|
+
#
|
186
|
+
# Product.where(price_in_bitcoins: Money.new(5, "GBP"))
|
187
|
+
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
|
188
|
+
#
|
189
|
+
# ==== Dirty Tracking
|
190
|
+
#
|
191
|
+
# The type of an attribute is given the opportunity to change how dirty
|
192
|
+
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
193
|
+
# will be called from ActiveModel::Dirty. See the documentation for those
|
194
|
+
# methods in ActiveModel::Type::Value for more details.
|
195
|
+
def attribute(name, cast_type = Type::Value.new, **options)
|
81
196
|
name = name.to_s
|
82
|
-
|
83
|
-
# Assign a new hash to ensure that subclasses do not share a hash
|
84
|
-
self.user_provided_columns = user_provided_columns.merge(name => cast_type)
|
85
|
-
|
86
|
-
if options.key?(:default)
|
87
|
-
self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Returns an array of column objects for the table associated with this class.
|
92
|
-
def columns
|
93
|
-
@columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
|
94
|
-
end
|
197
|
+
reload_schema_from_cache
|
95
198
|
|
96
|
-
|
97
|
-
|
98
|
-
|
199
|
+
self.attributes_to_define_after_schema_loads =
|
200
|
+
attributes_to_define_after_schema_loads.merge(
|
201
|
+
name => [cast_type, options]
|
202
|
+
)
|
99
203
|
end
|
100
204
|
|
101
|
-
|
102
|
-
|
205
|
+
# This is the low level API which sits beneath +attribute+. It only
|
206
|
+
# accepts type objects, and will do its work immediately instead of
|
207
|
+
# waiting for the schema to load. Automatic schema detection and
|
208
|
+
# ClassMethods#attribute both call this under the hood. While this method
|
209
|
+
# is provided so it can be used by plugin authors, application code
|
210
|
+
# should probably use ClassMethods#attribute.
|
211
|
+
#
|
212
|
+
# +name+ The name of the attribute being defined. Expected to be a +String+.
|
213
|
+
#
|
214
|
+
# +cast_type+ The type object to use for this attribute.
|
215
|
+
#
|
216
|
+
# +default+ The default value to use when no value is provided. If this option
|
217
|
+
# is not passed, the previous default value (if any) will be used.
|
218
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
219
|
+
# will be called once each time a new value is needed.
|
220
|
+
#
|
221
|
+
# +user_provided_default+ Whether the default value should be cast using
|
222
|
+
# +cast+ or +deserialize+.
|
223
|
+
def define_attribute(
|
224
|
+
name,
|
225
|
+
cast_type,
|
226
|
+
default: NO_DEFAULT_PROVIDED,
|
227
|
+
user_provided_default: true
|
228
|
+
)
|
229
|
+
attribute_types[name] = cast_type
|
230
|
+
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
103
231
|
end
|
104
232
|
|
105
|
-
def
|
233
|
+
def load_schema! # :nodoc:
|
106
234
|
super
|
107
|
-
|
235
|
+
attributes_to_define_after_schema_loads.each do |name, (type, options)|
|
236
|
+
if type.is_a?(Symbol)
|
237
|
+
type = ActiveRecord::Type.lookup(type, **options.except(:default))
|
238
|
+
end
|
239
|
+
|
240
|
+
define_attribute(name, type, **options.slice(:default))
|
241
|
+
end
|
108
242
|
end
|
109
243
|
|
110
244
|
private
|
111
245
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
246
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
247
|
+
private_constant :NO_DEFAULT_PROVIDED
|
248
|
+
|
249
|
+
def define_default_attribute(name, value, type, from_user:)
|
250
|
+
if value == NO_DEFAULT_PROVIDED
|
251
|
+
default_attribute = _default_attributes[name].with_type(type)
|
252
|
+
elsif from_user
|
253
|
+
default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
|
254
|
+
name,
|
255
|
+
value,
|
256
|
+
type,
|
257
|
+
_default_attributes.fetch(name.to_s) { nil },
|
258
|
+
)
|
117
259
|
else
|
118
|
-
|
260
|
+
default_attribute = ActiveModel::Attribute.from_database(name, value, type)
|
119
261
|
end
|
262
|
+
_default_attributes[name] = default_attribute
|
120
263
|
end
|
121
|
-
|
122
|
-
existing_column_names = existing_columns.map(&:name)
|
123
|
-
new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
|
124
|
-
connection.new_column(name, nil, type)
|
125
|
-
end
|
126
|
-
|
127
|
-
existing_columns + new_columns
|
128
|
-
end
|
129
|
-
|
130
|
-
def clear_caches_calculated_from_columns
|
131
|
-
@attributes_builder = nil
|
132
|
-
@column_names = nil
|
133
|
-
@column_types = nil
|
134
|
-
@columns = nil
|
135
|
-
@columns_hash = nil
|
136
|
-
@content_columns = nil
|
137
|
-
@default_attributes = nil
|
138
|
-
@persistable_attribute_names = nil
|
139
|
-
@attribute_names = nil
|
140
|
-
end
|
141
|
-
|
142
|
-
def raw_default_values
|
143
|
-
super.merge(user_provided_defaults)
|
144
|
-
end
|
145
264
|
end
|
146
265
|
end
|
147
266
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
# = Active Record Autosave Association
|
3
5
|
#
|
4
|
-
#
|
6
|
+
# AutosaveAssociation is a module that takes care of automatically saving
|
5
7
|
# associated records when their parent is saved. In addition to saving, it
|
6
8
|
# also destroys any associated records that were marked for destruction.
|
7
|
-
# (See
|
9
|
+
# (See #mark_for_destruction and #marked_for_destruction?).
|
8
10
|
#
|
9
11
|
# Saving of the parent, its associations, and the destruction of marked
|
10
12
|
# associations, all happen inside a transaction. This should never leave the
|
@@ -22,7 +24,7 @@ module ActiveRecord
|
|
22
24
|
#
|
23
25
|
# == Validation
|
24
26
|
#
|
25
|
-
#
|
27
|
+
# Child records are validated unless <tt>:validate</tt> is +false+.
|
26
28
|
#
|
27
29
|
# == Callbacks
|
28
30
|
#
|
@@ -125,7 +127,6 @@ module ActiveRecord
|
|
125
127
|
# Now it _is_ removed from the database:
|
126
128
|
#
|
127
129
|
# Comment.find_by(id: id).nil? # => true
|
128
|
-
|
129
130
|
module AutosaveAssociation
|
130
131
|
extend ActiveSupport::Concern
|
131
132
|
|
@@ -141,9 +142,10 @@ module ActiveRecord
|
|
141
142
|
|
142
143
|
included do
|
143
144
|
Associations::Builder::Association.extensions << AssociationBuilderExtension
|
145
|
+
mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
|
144
146
|
end
|
145
147
|
|
146
|
-
module ClassMethods
|
148
|
+
module ClassMethods # :nodoc:
|
147
149
|
private
|
148
150
|
|
149
151
|
def define_non_cyclic_method(name, &block)
|
@@ -153,10 +155,10 @@ module ActiveRecord
|
|
153
155
|
# Loop prevention for validation of associations
|
154
156
|
unless @_already_called[name]
|
155
157
|
begin
|
156
|
-
@_already_called[name]=true
|
158
|
+
@_already_called[name] = true
|
157
159
|
result = instance_eval(&block)
|
158
160
|
ensure
|
159
|
-
@_already_called[name]=false
|
161
|
+
@_already_called[name] = false
|
160
162
|
end
|
161
163
|
end
|
162
164
|
|
@@ -180,6 +182,7 @@ module ActiveRecord
|
|
180
182
|
|
181
183
|
if reflection.collection?
|
182
184
|
before_save :before_save_collection_association
|
185
|
+
after_save :after_save_collection_association
|
183
186
|
|
184
187
|
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
|
185
188
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
@@ -198,7 +201,7 @@ module ActiveRecord
|
|
198
201
|
after_create save_method
|
199
202
|
after_update save_method
|
200
203
|
else
|
201
|
-
define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
|
204
|
+
define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
|
202
205
|
before_save save_method
|
203
206
|
end
|
204
207
|
|
@@ -216,6 +219,7 @@ module ActiveRecord
|
|
216
219
|
|
217
220
|
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
218
221
|
validate validation_method
|
222
|
+
after_validation :_ensure_no_duplicate_errors
|
219
223
|
end
|
220
224
|
end
|
221
225
|
end
|
@@ -227,7 +231,7 @@ module ActiveRecord
|
|
227
231
|
super
|
228
232
|
end
|
229
233
|
|
230
|
-
# Marks this record to be destroyed as part of the
|
234
|
+
# Marks this record to be destroyed as part of the parent's save transaction.
|
231
235
|
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
|
232
236
|
# when <tt>parent.save</tt> is called.
|
233
237
|
#
|
@@ -236,7 +240,7 @@ module ActiveRecord
|
|
236
240
|
@marked_for_destruction = true
|
237
241
|
end
|
238
242
|
|
239
|
-
# Returns whether or not this record will be destroyed as part of the
|
243
|
+
# Returns whether or not this record will be destroyed as part of the parent's save transaction.
|
240
244
|
#
|
241
245
|
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
242
246
|
def marked_for_destruction?
|
@@ -259,7 +263,7 @@ module ActiveRecord
|
|
259
263
|
# Returns whether or not this record has been changed in any way (including whether
|
260
264
|
# any of its nested autosave associations are likewise changed)
|
261
265
|
def changed_for_autosave?
|
262
|
-
new_record? ||
|
266
|
+
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
|
263
267
|
end
|
264
268
|
|
265
269
|
private
|
@@ -268,12 +272,12 @@ module ActiveRecord
|
|
268
272
|
# or saved. If +autosave+ is +false+ only new records will be returned,
|
269
273
|
# unless the parent is/was a new record itself.
|
270
274
|
def associated_records_to_validate_or_save(association, new_record, autosave)
|
271
|
-
if new_record
|
275
|
+
if new_record || custom_validation_context?
|
272
276
|
association && association.target
|
273
277
|
elsif autosave
|
274
|
-
association.target.find_all
|
278
|
+
association.target.find_all(&:changed_for_autosave?)
|
275
279
|
else
|
276
|
-
association.target.find_all
|
280
|
+
association.target.find_all(&:new_record?)
|
277
281
|
end
|
278
282
|
end
|
279
283
|
|
@@ -300,7 +304,7 @@ module ActiveRecord
|
|
300
304
|
def validate_single_association(reflection)
|
301
305
|
association = association_instance_get(reflection.name)
|
302
306
|
record = association && association.reader
|
303
|
-
association_valid?(reflection, record) if record
|
307
|
+
association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
304
308
|
end
|
305
309
|
|
306
310
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -309,7 +313,7 @@ module ActiveRecord
|
|
309
313
|
def validate_collection_association(reflection)
|
310
314
|
if association = association_instance_get(reflection.name)
|
311
315
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
312
|
-
records.
|
316
|
+
records.each_with_index { |record, index| association_valid?(reflection, record, index) }
|
313
317
|
end
|
314
318
|
end
|
315
319
|
end
|
@@ -317,17 +321,30 @@ module ActiveRecord
|
|
317
321
|
# Returns whether or not the association is valid and applies any errors to
|
318
322
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
319
323
|
# enabled records if they're marked_for_destruction? or destroyed.
|
320
|
-
def association_valid?(reflection, record)
|
324
|
+
def association_valid?(reflection, record, index = nil)
|
321
325
|
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
|
322
326
|
|
323
|
-
|
324
|
-
|
327
|
+
context = validation_context if custom_validation_context?
|
328
|
+
|
329
|
+
unless valid = record.valid?(context)
|
325
330
|
if reflection.options[:autosave]
|
331
|
+
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
|
332
|
+
|
326
333
|
record.errors.each do |attribute, message|
|
327
|
-
attribute =
|
334
|
+
attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
328
335
|
errors[attribute] << message
|
329
336
|
errors[attribute].uniq!
|
330
337
|
end
|
338
|
+
|
339
|
+
record.errors.details.each_key do |attribute|
|
340
|
+
reflection_attribute =
|
341
|
+
normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
|
342
|
+
|
343
|
+
record.errors.details[attribute].each do |error|
|
344
|
+
errors.details[reflection_attribute] << error
|
345
|
+
errors.details[reflection_attribute].uniq!
|
346
|
+
end
|
347
|
+
end
|
331
348
|
else
|
332
349
|
errors.add(reflection.name)
|
333
350
|
end
|
@@ -335,18 +352,29 @@ module ActiveRecord
|
|
335
352
|
valid
|
336
353
|
end
|
337
354
|
|
355
|
+
def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
356
|
+
if indexed_attribute
|
357
|
+
"#{reflection.name}[#{index}].#{attribute}"
|
358
|
+
else
|
359
|
+
"#{reflection.name}.#{attribute}"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
338
363
|
# Is used as a before_save callback to check while saving a collection
|
339
364
|
# association whether or not the parent was a new record before saving.
|
340
365
|
def before_save_collection_association
|
341
366
|
@new_record_before_save = new_record?
|
342
|
-
|
367
|
+
end
|
368
|
+
|
369
|
+
def after_save_collection_association
|
370
|
+
@new_record_before_save = false
|
343
371
|
end
|
344
372
|
|
345
373
|
# Saves any new associated records, or all loaded autosave associations if
|
346
374
|
# <tt>:autosave</tt> is enabled on the association.
|
347
375
|
#
|
348
376
|
# In addition, it destroys all children that were marked for destruction
|
349
|
-
# with mark_for_destruction.
|
377
|
+
# with #mark_for_destruction.
|
350
378
|
#
|
351
379
|
# This all happens inside a transaction, _if_ the Transactions module is included into
|
352
380
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
@@ -354,7 +382,14 @@ module ActiveRecord
|
|
354
382
|
if association = association_instance_get(reflection.name)
|
355
383
|
autosave = reflection.options[:autosave]
|
356
384
|
|
357
|
-
|
385
|
+
# By saving the instance variable in a local variable,
|
386
|
+
# we make the whole callback re-entrant.
|
387
|
+
new_record_before_save = @new_record_before_save
|
388
|
+
|
389
|
+
# reconstruct the scope now that we know the owner's id
|
390
|
+
association.reset_scope
|
391
|
+
|
392
|
+
if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
|
358
393
|
if autosave
|
359
394
|
records_to_destroy = records.select(&:marked_for_destruction?)
|
360
395
|
records_to_destroy.each { |record| association.destroy(record) }
|
@@ -366,22 +401,24 @@ module ActiveRecord
|
|
366
401
|
|
367
402
|
saved = true
|
368
403
|
|
369
|
-
if autosave != false && (
|
404
|
+
if autosave != false && (new_record_before_save || record.new_record?)
|
370
405
|
if autosave
|
371
406
|
saved = association.insert_record(record, false)
|
372
|
-
|
373
|
-
association.insert_record(record)
|
407
|
+
elsif !reflection.nested?
|
408
|
+
association_saved = association.insert_record(record)
|
409
|
+
|
410
|
+
if reflection.validate?
|
411
|
+
errors.add(reflection.name) unless association_saved
|
412
|
+
saved = association_saved
|
413
|
+
end
|
374
414
|
end
|
375
415
|
elsif autosave
|
376
|
-
saved = record.save(:
|
416
|
+
saved = record.save(validate: false)
|
377
417
|
end
|
378
418
|
|
379
419
|
raise ActiveRecord::Rollback unless saved
|
380
420
|
end
|
381
421
|
end
|
382
|
-
|
383
|
-
# reconstruct the scope now that we know the owner's id
|
384
|
-
association.reset_scope if association.respond_to?(:reset_scope)
|
385
422
|
end
|
386
423
|
end
|
387
424
|
|
@@ -389,7 +426,7 @@ module ActiveRecord
|
|
389
426
|
# on the association.
|
390
427
|
#
|
391
428
|
# In addition, it will destroy the association if it was marked for
|
392
|
-
# destruction with mark_for_destruction.
|
429
|
+
# destruction with #mark_for_destruction.
|
393
430
|
#
|
394
431
|
# This all happens inside a transaction, _if_ the Transactions module is included into
|
395
432
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
@@ -408,9 +445,12 @@ module ActiveRecord
|
|
408
445
|
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
|
409
446
|
unless reflection.through_reflection
|
410
447
|
record[reflection.foreign_key] = key
|
448
|
+
if inverse_reflection = reflection.inverse_of
|
449
|
+
record.association(inverse_reflection.name).loaded!
|
450
|
+
end
|
411
451
|
end
|
412
452
|
|
413
|
-
saved = record.save(:
|
453
|
+
saved = record.save(validate: !autosave)
|
414
454
|
raise ActiveRecord::Rollback if !saved && autosave
|
415
455
|
saved
|
416
456
|
end
|
@@ -421,8 +461,14 @@ module ActiveRecord
|
|
421
461
|
# If the record is new or it has changed, returns true.
|
422
462
|
def record_changed?(reflection, record, key)
|
423
463
|
record.new_record? ||
|
424
|
-
|
425
|
-
record.
|
464
|
+
association_foreign_key_changed?(reflection, record, key) ||
|
465
|
+
record.will_save_change_to_attribute?(reflection.foreign_key)
|
466
|
+
end
|
467
|
+
|
468
|
+
def association_foreign_key_changed?(reflection, record, key)
|
469
|
+
return false if reflection.through_reflection?
|
470
|
+
|
471
|
+
record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
|
426
472
|
end
|
427
473
|
|
428
474
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
@@ -430,7 +476,9 @@ module ActiveRecord
|
|
430
476
|
# In addition, it will destroy the association if it was marked for destruction.
|
431
477
|
def save_belongs_to_association(reflection)
|
432
478
|
association = association_instance_get(reflection.name)
|
433
|
-
|
479
|
+
return unless association && association.loaded? && !association.stale_target?
|
480
|
+
|
481
|
+
record = association.load_target
|
434
482
|
if record && !record.destroyed?
|
435
483
|
autosave = reflection.options[:autosave]
|
436
484
|
|
@@ -438,7 +486,7 @@ module ActiveRecord
|
|
438
486
|
self[reflection.foreign_key] = nil
|
439
487
|
record.destroy
|
440
488
|
elsif autosave != false
|
441
|
-
saved = record.save(:
|
489
|
+
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
442
490
|
|
443
491
|
if association.updated?
|
444
492
|
association_id = record.send(reflection.options[:primary_key] || :id)
|
@@ -450,5 +498,15 @@ module ActiveRecord
|
|
450
498
|
end
|
451
499
|
end
|
452
500
|
end
|
501
|
+
|
502
|
+
def custom_validation_context?
|
503
|
+
validation_context && [:create, :update].exclude?(validation_context)
|
504
|
+
end
|
505
|
+
|
506
|
+
def _ensure_no_duplicate_errors
|
507
|
+
errors.messages.each_key do |attribute|
|
508
|
+
errors[attribute].uniq!
|
509
|
+
end
|
510
|
+
end
|
453
511
|
end
|
454
512
|
end
|