activerecord 4.2.0 → 5.2.8.1
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 +5 -5
- data/CHANGELOG.md +640 -928
- 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 +264 -247
- data/lib/active_record/association_relation.rb +24 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +87 -41
- data/lib/active_record/associations/association_scope.rb +106 -132
- data/lib/active_record/associations/belongs_to_association.rb +55 -36
- 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 +14 -23
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
- 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 +145 -266
- data/lib/active_record/associations/collection_proxy.rb +242 -138
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +35 -75
- data/lib/active_record/associations/has_many_through_association.rb +51 -69
- data/lib/active_record/associations/has_one_association.rb +39 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
- 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 +134 -154
- data/lib/active_record/associations/preloader/association.rb +85 -116
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +83 -93
- data/lib/active_record/associations/singular_association.rb +27 -40
- data/lib/active_record/associations/through_association.rb +48 -23
- data/lib/active_record/associations.rb +1732 -1596
- 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 +12 -5
- data/lib/active_record/attribute_methods/dirty.rb +94 -125
- 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 +62 -36
- data/lib/active_record/attribute_methods/write.rb +31 -46
- data/lib/active_record/attribute_methods.rb +170 -117
- data/lib/active_record/attributes.rb +201 -74
- data/lib/active_record/autosave_association.rb +118 -45
- data/lib/active_record/base.rb +60 -48
- 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 +37 -13
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
- data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
- 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 +42 -195
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
- 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 +5 -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 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 +7 -9
- 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 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
- 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 +466 -280
- 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 +439 -330
- 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 -324
- 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 +205 -202
- data/lib/active_record/counter_cache.rb +80 -37
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +136 -90
- data/lib/active_record/errors.rb +180 -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 +11 -6
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +193 -135
- 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 +48 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +92 -98
- 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 +594 -267
- data/lib/active_record/model_schema.rb +292 -111
- data/lib/active_record/nested_attributes.rb +266 -214
- data/lib/active_record/no_touching.rb +8 -2
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +350 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +117 -35
- 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 +160 -174
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +447 -288
- 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 +259 -244
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +290 -253
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +91 -68
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
- 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 +118 -92
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +446 -389
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -16
- 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 -339
- 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 -19
- data/lib/active_record/scoping/default.rb +102 -84
- 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 +136 -95
- data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
- 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 +208 -123
- 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 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
- 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 +30 -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 +41 -32
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +36 -21
- 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 -6
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
- 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.rb +7 -5
- metadata +77 -53
- 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 -149
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- 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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- 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 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,212 +1,88 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/forbidden_attributes_protection"
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
module AttributeAssignment
|
5
7
|
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::
|
7
|
-
|
8
|
-
# Allows you to set all the attributes by passing in a hash of attributes with
|
9
|
-
# keys matching the attribute names (which again matches the column names).
|
10
|
-
#
|
11
|
-
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
|
-
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
-
# exception is raised.
|
14
|
-
#
|
15
|
-
# cat = Cat.new(name: "Gorby", status: "yawning")
|
16
|
-
# cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
|
17
|
-
# cat.assign_attributes(status: "sleeping")
|
18
|
-
# cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
|
19
|
-
#
|
20
|
-
# New attributes will be persisted in the database when the object is saved.
|
21
|
-
#
|
22
|
-
# Aliased to <tt>attributes=</tt>.
|
23
|
-
def assign_attributes(new_attributes)
|
24
|
-
if !new_attributes.respond_to?(:stringify_keys)
|
25
|
-
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
26
|
-
end
|
27
|
-
return if new_attributes.blank?
|
28
|
-
|
29
|
-
attributes = new_attributes.stringify_keys
|
30
|
-
multi_parameter_attributes = []
|
31
|
-
nested_parameter_attributes = []
|
32
|
-
|
33
|
-
attributes = sanitize_for_mass_assignment(attributes)
|
34
|
-
|
35
|
-
attributes.each do |k, v|
|
36
|
-
if k.include?("(")
|
37
|
-
multi_parameter_attributes << [ k, v ]
|
38
|
-
elsif v.is_a?(Hash)
|
39
|
-
nested_parameter_attributes << [ k, v ]
|
40
|
-
else
|
41
|
-
_assign_attribute(k, v)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
46
|
-
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
47
|
-
end
|
48
|
-
|
49
|
-
alias attributes= assign_attributes
|
8
|
+
include ActiveModel::AttributeAssignment
|
50
9
|
|
51
10
|
private
|
52
11
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if respond_to?("#{k}=")
|
57
|
-
raise
|
58
|
-
else
|
59
|
-
raise UnknownAttributeError.new(self, k)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Assign any deferred nested attributes after the base attributes have been set.
|
64
|
-
def assign_nested_parameter_attributes(pairs)
|
65
|
-
pairs.each { |k, v| _assign_attribute(k, v) }
|
66
|
-
end
|
67
|
-
|
68
|
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
69
|
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
70
|
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
71
|
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
72
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
|
73
|
-
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
74
|
-
def assign_multiparameter_attributes(pairs)
|
75
|
-
execute_callstack_for_multiparameter_attributes(
|
76
|
-
extract_callstack_for_multiparameter_attributes(pairs)
|
77
|
-
)
|
78
|
-
end
|
12
|
+
def _assign_attributes(attributes)
|
13
|
+
multi_parameter_attributes = {}
|
14
|
+
nested_parameter_attributes = {}
|
79
15
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
16
|
+
attributes.each do |k, v|
|
17
|
+
if k.include?("(")
|
18
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
19
|
+
elsif v.is_a?(Hash)
|
20
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
21
|
+
end
|
87
22
|
end
|
88
|
-
|
89
|
-
unless errors.empty?
|
90
|
-
error_descriptions = errors.map { |ex| ex.message }.join(",")
|
91
|
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def extract_callstack_for_multiparameter_attributes(pairs)
|
96
|
-
attributes = {}
|
23
|
+
super(attributes)
|
97
24
|
|
98
|
-
|
99
|
-
|
100
|
-
attributes[attribute_name] ||= {}
|
101
|
-
|
102
|
-
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
103
|
-
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
25
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
26
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
104
27
|
end
|
105
28
|
|
106
|
-
attributes
|
107
|
-
|
108
|
-
|
109
|
-
def type_cast_attribute_value(multiparameter_name, value)
|
110
|
-
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
111
|
-
end
|
112
|
-
|
113
|
-
def find_parameter_position(multiparameter_name)
|
114
|
-
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
115
|
-
end
|
116
|
-
|
117
|
-
class MultiparameterAttribute #:nodoc:
|
118
|
-
attr_reader :object, :name, :values, :cast_type
|
119
|
-
|
120
|
-
def initialize(object, name, values)
|
121
|
-
@object = object
|
122
|
-
@name = name
|
123
|
-
@values = values
|
29
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
30
|
+
def assign_nested_parameter_attributes(pairs)
|
31
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
124
32
|
end
|
125
33
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
else
|
137
|
-
read_other
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
def instantiate_time_object(set_values)
|
144
|
-
if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
|
145
|
-
Time.zone.local(*set_values)
|
146
|
-
else
|
147
|
-
Time.send(object.class.default_timezone, *set_values)
|
148
|
-
end
|
34
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
35
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
36
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
37
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
38
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
39
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
40
|
+
def assign_multiparameter_attributes(pairs)
|
41
|
+
execute_callstack_for_multiparameter_attributes(
|
42
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
43
|
+
)
|
149
44
|
end
|
150
45
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
46
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
47
|
+
errors = []
|
48
|
+
callstack.each do |name, values_with_empty_parameters|
|
49
|
+
begin
|
50
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
51
|
+
values = nil
|
52
|
+
else
|
53
|
+
values = values_with_empty_parameters
|
54
|
+
end
|
55
|
+
send("#{name}=", values)
|
56
|
+
rescue => ex
|
57
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
158
58
|
end
|
159
|
-
else
|
160
|
-
# else column is a timestamp, so if Date bits were not provided, error
|
161
|
-
validate_required_parameters!([1,2,3])
|
162
|
-
|
163
|
-
# If Date bits were provided but blank, then return nil
|
164
|
-
return if blank_date_parameter?
|
165
59
|
end
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# If Time bits are not there, then default to 0
|
170
|
-
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
|
171
|
-
instantiate_time_object(set_values)
|
172
|
-
end
|
173
|
-
|
174
|
-
def read_date
|
175
|
-
return if blank_date_parameter?
|
176
|
-
set_values = values.values_at(1,2,3)
|
177
|
-
begin
|
178
|
-
Date.new(*set_values)
|
179
|
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
180
|
-
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
60
|
+
unless errors.empty?
|
61
|
+
error_descriptions = errors.map(&:message).join(",")
|
62
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
181
63
|
end
|
182
64
|
end
|
183
65
|
|
184
|
-
def
|
185
|
-
|
186
|
-
positions = (1..max_position)
|
187
|
-
validate_required_parameters!(positions)
|
66
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
67
|
+
attributes = {}
|
188
68
|
|
189
|
-
|
190
|
-
|
69
|
+
pairs.each do |(multiparameter_name, value)|
|
70
|
+
attribute_name = multiparameter_name.split("(").first
|
71
|
+
attributes[attribute_name] ||= {}
|
72
|
+
|
73
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
74
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
75
|
+
end
|
191
76
|
|
192
|
-
|
193
|
-
# than the validate_required_parameters! method, since it just checks for blank
|
194
|
-
# positions instead of missing ones, and does not raise in case one blank position
|
195
|
-
# exists. The caller is responsible to handle the case of this returning true.
|
196
|
-
def blank_date_parameter?
|
197
|
-
(1..3).any? { |position| values[position].blank? }
|
77
|
+
attributes
|
198
78
|
end
|
199
79
|
|
200
|
-
|
201
|
-
|
202
|
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
203
|
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
204
|
-
end
|
80
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
81
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
205
82
|
end
|
206
83
|
|
207
|
-
def
|
208
|
-
[
|
84
|
+
def find_parameter_position(multiparameter_name)
|
85
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
209
86
|
end
|
210
|
-
end
|
211
87
|
end
|
212
88
|
end
|
@@ -1,21 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeDecorators # :nodoc:
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
included do
|
6
|
-
class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
|
7
|
-
self.attribute_type_decorations = TypeDecorator.new
|
8
|
+
class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal:
|
8
9
|
end
|
9
10
|
|
10
11
|
module ClassMethods # :nodoc:
|
12
|
+
# This method is an internal API used to create class macros such as
|
13
|
+
# +serialize+, and features like time zone aware attributes.
|
14
|
+
#
|
15
|
+
# Used to wrap the type of an attribute in a new type.
|
16
|
+
# When the schema for a model is loaded, attributes with the same name as
|
17
|
+
# +column_name+ will have their type yielded to the given block. The
|
18
|
+
# return value of that block will be used instead.
|
19
|
+
#
|
20
|
+
# Subsequent calls where +column_name+ and +decorator_name+ are the same
|
21
|
+
# will override the previous decorator, not decorate twice. This can be
|
22
|
+
# used to create idempotent class macros like +serialize+
|
11
23
|
def decorate_attribute_type(column_name, decorator_name, &block)
|
12
24
|
matcher = ->(name, _) { name == column_name.to_s }
|
13
25
|
key = "_#{column_name}_#{decorator_name}"
|
14
26
|
decorate_matching_attribute_types(matcher, key, &block)
|
15
27
|
end
|
16
28
|
|
29
|
+
# This method is an internal API used to create higher level features like
|
30
|
+
# time zone aware attributes.
|
31
|
+
#
|
32
|
+
# When the schema for a model is loaded, +matcher+ will be called for each
|
33
|
+
# attribute with its name and type. If the matcher returns a truthy value,
|
34
|
+
# the type will then be yielded to the given block, and the return value
|
35
|
+
# of that block will replace the type.
|
36
|
+
#
|
37
|
+
# Subsequent calls to this method with the same value for +decorator_name+
|
38
|
+
# will replace the previous decorator, not decorate twice. This can be
|
39
|
+
# used to ensure that class macros are idempotent.
|
17
40
|
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
18
|
-
|
41
|
+
reload_schema_from_cache
|
19
42
|
decorator_name = decorator_name.to_s
|
20
43
|
|
21
44
|
# Create new hashes so we don't modify parent classes
|
@@ -24,12 +47,13 @@ module ActiveRecord
|
|
24
47
|
|
25
48
|
private
|
26
49
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
50
|
+
def load_schema!
|
51
|
+
super
|
52
|
+
attribute_types.each do |name, type|
|
53
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
54
|
+
define_attribute(name, decorated_type)
|
55
|
+
end
|
31
56
|
end
|
32
|
-
end
|
33
57
|
end
|
34
58
|
|
35
59
|
class TypeDecorator # :nodoc:
|
@@ -52,15 +76,15 @@ module ActiveRecord
|
|
52
76
|
|
53
77
|
private
|
54
78
|
|
55
|
-
|
56
|
-
|
57
|
-
|
79
|
+
def decorators_for(name, type)
|
80
|
+
matching(name, type).map(&:last)
|
81
|
+
end
|
58
82
|
|
59
|
-
|
60
|
-
|
61
|
-
|
83
|
+
def matching(name, type)
|
84
|
+
@decorations.values.select do |(matcher, _)|
|
85
|
+
matcher.call(name, type)
|
86
|
+
end
|
62
87
|
end
|
63
|
-
end
|
64
88
|
end
|
65
89
|
end
|
66
90
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeMethods
|
3
5
|
# = Active Record Attribute Methods Before Type Cast
|
4
6
|
#
|
5
|
-
#
|
7
|
+
# ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
|
6
8
|
# read the value of the attributes before typecasting and deserialization.
|
7
9
|
#
|
8
10
|
# class Task < ActiveRecord::Base
|
@@ -28,6 +30,7 @@ module ActiveRecord
|
|
28
30
|
|
29
31
|
included do
|
30
32
|
attribute_method_suffix "_before_type_cast"
|
33
|
+
attribute_method_suffix "_came_from_user?"
|
31
34
|
end
|
32
35
|
|
33
36
|
# Returns the value of the attribute identified by +attr_name+ before
|
@@ -62,10 +65,14 @@ module ActiveRecord
|
|
62
65
|
|
63
66
|
private
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
# Handle *_before_type_cast for method_missing.
|
69
|
+
def attribute_before_type_cast(attribute_name)
|
70
|
+
read_attribute_before_type_cast(attribute_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def attribute_came_from_user?(attribute_name)
|
74
|
+
@attributes[attribute_name].came_from_user?
|
75
|
+
end
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/attribute_accessors"
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
module AttributeMethods
|
5
|
-
module Dirty
|
7
|
+
module Dirty
|
6
8
|
extend ActiveSupport::Concern
|
7
9
|
|
8
10
|
include ActiveModel::Dirty
|
@@ -12,170 +14,137 @@ module ActiveRecord
|
|
12
14
|
raise "You cannot include Dirty after Timestamp"
|
13
15
|
end
|
14
16
|
|
15
|
-
class_attribute :partial_writes, instance_writer: false
|
16
|
-
self.partial_writes = true
|
17
|
-
end
|
17
|
+
class_attribute :partial_writes, instance_writer: false, default: true
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
status
|
25
|
-
end
|
19
|
+
# Attribute methods for "changed in last call to save?"
|
20
|
+
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
|
21
|
+
attribute_method_prefix("saved_change_to_")
|
22
|
+
attribute_method_suffix("_before_last_save")
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
changes_applied
|
31
|
-
end
|
24
|
+
# Attribute methods for "will change if I call save?"
|
25
|
+
attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
|
26
|
+
attribute_method_suffix("_change_to_be_saved", "_in_database")
|
32
27
|
end
|
33
28
|
|
34
29
|
# <tt>reload</tt> the record and clears changed attributes.
|
35
30
|
def reload(*)
|
36
31
|
super.tap do
|
37
|
-
|
32
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
33
|
+
@mutations_before_last_save = nil
|
34
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
35
|
+
@mutations_from_database = nil
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
# Did this attribute change when we last saved? This method can be invoked
|
40
|
+
# as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
|
41
|
+
# Behaves similarly to +attribute_changed?+. This method is useful in
|
42
|
+
# after callbacks to determine if the call to save changed a certain
|
43
|
+
# attribute.
|
44
|
+
#
|
45
|
+
# ==== Options
|
46
|
+
#
|
47
|
+
# +from+ When passed, this method will return false unless the original
|
48
|
+
# value is equal to the given option
|
49
|
+
#
|
50
|
+
# +to+ When passed, this method will return false unless the value was
|
51
|
+
# changed to the given value
|
52
|
+
def saved_change_to_attribute?(attr_name, **options)
|
53
|
+
mutations_before_last_save.changed?(attr_name, **options)
|
44
54
|
end
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
|
56
|
+
# Returns the change to an attribute during the last save. If the
|
57
|
+
# attribute was changed, the result will be an array containing the
|
58
|
+
# original value and the saved value.
|
59
|
+
#
|
60
|
+
# Behaves similarly to +attribute_change+. This method is useful in after
|
61
|
+
# callbacks, to see the change in an attribute that just occurred
|
62
|
+
#
|
63
|
+
# This method can be invoked as +saved_change_to_name+ in instead of
|
64
|
+
# <tt>saved_change_to_attribute("name")</tt>
|
65
|
+
def saved_change_to_attribute(attr_name)
|
66
|
+
mutations_before_last_save.change_to_attribute(attr_name)
|
49
67
|
end
|
50
68
|
|
51
|
-
|
52
|
-
|
53
|
-
|
69
|
+
# Returns the original value of an attribute before the last save.
|
70
|
+
# Behaves similarly to +attribute_was+. This method is useful in after
|
71
|
+
# callbacks to get the original value of an attribute before the save that
|
72
|
+
# just occurred
|
73
|
+
def attribute_before_last_save(attr_name)
|
74
|
+
mutations_before_last_save.original_value(attr_name)
|
54
75
|
end
|
55
76
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
if defined?(@cached_changed_attributes)
|
60
|
-
@cached_changed_attributes
|
61
|
-
else
|
62
|
-
super.reverse_merge(attributes_changed_in_place).freeze
|
63
|
-
end
|
77
|
+
# Did the last call to +save+ have any changes to change?
|
78
|
+
def saved_changes?
|
79
|
+
mutations_before_last_save.any_changes?
|
64
80
|
end
|
65
81
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
82
|
+
# Returns a hash containing all the changes that were just saved.
|
83
|
+
def saved_changes
|
84
|
+
mutations_before_last_save.changes
|
70
85
|
end
|
71
86
|
|
72
|
-
|
73
|
-
|
74
|
-
|
87
|
+
# Alias for +attribute_changed?+
|
88
|
+
def will_save_change_to_attribute?(attr_name, **options)
|
89
|
+
mutations_from_database.changed?(attr_name, **options)
|
75
90
|
end
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@changed_attributes = nil
|
81
|
-
self.class.column_defaults.each do |attr, orig_value|
|
82
|
-
set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
|
83
|
-
end
|
92
|
+
# Alias for +attribute_change+
|
93
|
+
def attribute_change_to_be_saved(attr_name)
|
94
|
+
mutations_from_database.change_to_attribute(attr_name)
|
84
95
|
end
|
85
96
|
|
86
|
-
#
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
old_value = old_attribute_value(attr)
|
91
|
-
|
92
|
-
result = super
|
93
|
-
store_original_raw_attribute(attr)
|
94
|
-
save_changed_attribute(attr, old_value)
|
95
|
-
result
|
97
|
+
# Alias for +attribute_was+
|
98
|
+
def attribute_in_database(attr_name)
|
99
|
+
mutations_from_database.original_value(attr_name)
|
96
100
|
end
|
97
101
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
result = super
|
102
|
-
original_raw_attributes[attr] = value
|
103
|
-
result
|
102
|
+
# Alias for +changed?+
|
103
|
+
def has_changes_to_save?
|
104
|
+
mutations_from_database.any_changes?
|
104
105
|
end
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
else
|
110
|
-
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
|
111
|
-
end
|
107
|
+
# Alias for +changes+
|
108
|
+
def changes_to_save
|
109
|
+
mutations_from_database.changes
|
112
110
|
end
|
113
111
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
else
|
118
|
-
clone_attribute_value(:_read_attribute, attr)
|
119
|
-
end
|
112
|
+
# Alias for +changed+
|
113
|
+
def changed_attribute_names_to_save
|
114
|
+
mutations_from_database.changed_attribute_names
|
120
115
|
end
|
121
116
|
|
122
|
-
|
123
|
-
|
117
|
+
# Alias for +changed_attributes+
|
118
|
+
def attributes_in_database
|
119
|
+
mutations_from_database.changed_values
|
124
120
|
end
|
125
121
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
def _field_changed?(attr, old_value)
|
137
|
-
@attributes[attr].changed_from?(old_value)
|
138
|
-
end
|
139
|
-
|
140
|
-
def attributes_changed_in_place
|
141
|
-
changed_in_place.each_with_object({}) do |attr_name, h|
|
142
|
-
orig = @attributes[attr_name].original_value
|
143
|
-
h[attr_name] = orig
|
122
|
+
private
|
123
|
+
def write_attribute_without_type_cast(attr_name, value)
|
124
|
+
name = attr_name.to_s
|
125
|
+
if self.class.attribute_alias?(name)
|
126
|
+
name = self.class.attribute_alias(name)
|
127
|
+
end
|
128
|
+
result = super(name, value)
|
129
|
+
clear_attribute_change(name)
|
130
|
+
result
|
144
131
|
end
|
145
|
-
end
|
146
132
|
|
147
|
-
|
148
|
-
|
149
|
-
|
133
|
+
def _update_record(*)
|
134
|
+
affected_rows = partial_writes? ? super(keys_for_partial_write) : super
|
135
|
+
changes_applied
|
136
|
+
affected_rows
|
150
137
|
end
|
151
|
-
end
|
152
138
|
|
153
|
-
|
154
|
-
|
155
|
-
|
139
|
+
def _create_record(*)
|
140
|
+
id = partial_writes? ? super(keys_for_partial_write) : super
|
141
|
+
changes_applied
|
142
|
+
id
|
156
143
|
end
|
157
|
-
end
|
158
144
|
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
def store_original_raw_attribute(attr_name)
|
164
|
-
original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
|
165
|
-
end
|
166
|
-
|
167
|
-
def store_original_raw_attributes
|
168
|
-
attribute_names.each do |attr|
|
169
|
-
store_original_raw_attribute(attr)
|
145
|
+
def keys_for_partial_write
|
146
|
+
changed_attribute_names_to_save & self.class.column_names
|
170
147
|
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def cache_changed_attributes
|
174
|
-
@cached_changed_attributes = changed_attributes
|
175
|
-
yield
|
176
|
-
ensure
|
177
|
-
remove_instance_variable(:@cached_changed_attributes)
|
178
|
-
end
|
179
148
|
end
|
180
149
|
end
|
181
150
|
end
|