activerecord 4.2.11.1 → 5.0.0
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 +1282 -1195
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record.rb +8 -4
- data/lib/active_record/aggregations.rb +35 -24
- data/lib/active_record/association_relation.rb +3 -3
- data/lib/active_record/associations.rb +317 -209
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +11 -9
- data/lib/active_record/associations/association_scope.rb +73 -102
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -10
- data/lib/active_record/associations/collection_association.rb +49 -41
- data/lib/active_record/associations/collection_proxy.rb +67 -27
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +20 -71
- data/lib/active_record/associations/has_many_through_association.rb +8 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +29 -19
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +46 -52
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +27 -14
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +11 -3
- data/lib/active_record/attribute.rb +68 -18
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +76 -47
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +30 -3
- data/lib/active_record/attribute_set/builder.rb +6 -4
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +49 -16
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +89 -107
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +113 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +76 -40
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +43 -21
- data/lib/active_record/migration.rb +363 -133
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/model_schema.rb +129 -41
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +121 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +69 -46
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +282 -115
- data/lib/active_record/relation.rb +176 -116
- data/lib/active_record/relation/batches.rb +139 -34
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +79 -108
- data/lib/active_record/relation/delegation.rb +7 -20
- data/lib/active_record/relation/finder_methods.rb +163 -81
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +95 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +62 -38
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +32 -15
- data/lib/active_record/scoping/default.rb +23 -9
- data/lib/active_record/scoping/named.rb +49 -28
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +16 -14
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +57 -43
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +138 -56
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +15 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +30 -29
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +59 -34
- 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/date.rb +0 -11
- 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/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- 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/decimal_without_scale.rb +0 -11
- 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/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class HasOne < SingularAssociation #:nodoc:
|
5
|
-
|
6
5
|
def association_key_name
|
7
6
|
reflection.foreign_key
|
8
7
|
end
|
@@ -10,13 +9,6 @@ module ActiveRecord
|
|
10
9
|
def owner_key_name
|
11
10
|
reflection.active_record_primary_key
|
12
11
|
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def build_scope
|
17
|
-
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
18
|
-
end
|
19
|
-
|
20
12
|
end
|
21
13
|
end
|
22
14
|
end
|
@@ -18,7 +18,8 @@ module ActiveRecord
|
|
18
18
|
through_records = owners.map do |owner|
|
19
19
|
association = owner.association through_reflection.name
|
20
20
|
|
21
|
-
|
21
|
+
center = target_records_from_association(association)
|
22
|
+
[owner, Array(center)]
|
22
23
|
end
|
23
24
|
|
24
25
|
reset_association owners, through_reflection.name
|
@@ -37,28 +38,35 @@ module ActiveRecord
|
|
37
38
|
}
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
-
@preloaded_records.each_with_index do |record,i|
|
42
|
-
record_offset[record] = i
|
43
|
-
end
|
44
|
-
|
45
|
-
through_records.each_with_object({}) { |(lhs,center),records_by_owner|
|
41
|
+
through_records.each_with_object({}) do |(lhs,center), records_by_owner|
|
46
42
|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
|
47
43
|
|
48
44
|
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
|
49
45
|
rhs_records = middles.flat_map { |r|
|
50
46
|
association = r.association source_reflection.name
|
51
47
|
|
52
|
-
association
|
48
|
+
target_records_from_association(association)
|
53
49
|
}.compact
|
54
50
|
|
55
|
-
|
51
|
+
# Respect the order on `reflection_scope` if it exists, else use the natural order.
|
52
|
+
if reflection_scope.values[:order].present?
|
53
|
+
@id_map ||= id_to_index_map @preloaded_records
|
54
|
+
rhs_records.sort_by { |rhs| @id_map[rhs] }
|
55
|
+
else
|
56
|
+
rhs_records
|
57
|
+
end
|
56
58
|
end
|
57
|
-
|
59
|
+
end
|
58
60
|
end
|
59
61
|
|
60
62
|
private
|
61
63
|
|
64
|
+
def id_to_index_map(ids)
|
65
|
+
id_map = {}
|
66
|
+
ids.each_with_index { |id, index| id_map[id] = index }
|
67
|
+
id_map
|
68
|
+
end
|
69
|
+
|
62
70
|
def reset_association(owners, association_name)
|
63
71
|
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
72
|
(reflection.options[:source_type] && through_reflection.collection?)
|
@@ -78,18 +86,23 @@ module ActiveRecord
|
|
78
86
|
if options[:source_type]
|
79
87
|
scope.where! reflection.foreign_type => options[:source_type]
|
80
88
|
else
|
81
|
-
unless reflection_scope.
|
89
|
+
unless reflection_scope.where_clause.empty?
|
82
90
|
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
83
|
-
scope.
|
84
|
-
scope.bind_values = reflection_scope.bind_values
|
91
|
+
scope.where_clause = reflection_scope.where_clause
|
85
92
|
end
|
86
93
|
|
87
94
|
scope.references! reflection_scope.values[:references]
|
88
|
-
scope =
|
95
|
+
if scope.eager_loading? && order_values = reflection_scope.values[:order]
|
96
|
+
scope = scope.order(order_values)
|
97
|
+
end
|
89
98
|
end
|
90
99
|
|
91
100
|
scope
|
92
101
|
end
|
102
|
+
|
103
|
+
def target_records_from_association(association)
|
104
|
+
association.loaded? ? association.target : association.reader
|
105
|
+
end
|
93
106
|
end
|
94
107
|
end
|
95
108
|
end
|
@@ -4,6 +4,12 @@ module ActiveRecord
|
|
4
4
|
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
5
|
def reader(force_reload = false)
|
6
6
|
if force_reload && klass
|
7
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
8
|
+
Passing an argument to force an association to reload is now
|
9
|
+
deprecated and will be removed in Rails 5.1. Please call `reload`
|
10
|
+
on the parent object instead.
|
11
|
+
MSG
|
12
|
+
|
7
13
|
klass.uncached { reload }
|
8
14
|
elsif !loaded? || stale_target?
|
9
15
|
reload
|
@@ -39,7 +45,7 @@ module ActiveRecord
|
|
39
45
|
end
|
40
46
|
|
41
47
|
def get_records
|
42
|
-
return scope.limit(1).
|
48
|
+
return scope.limit(1).records if skip_statement_cache?
|
43
49
|
|
44
50
|
conn = klass.connection
|
45
51
|
sc = reflection.association_scope_cache(conn, owner) do
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
# Construct attributes for :through pointing to owner and associate. This is used by the
|
28
28
|
# methods which create and delete records on the association.
|
29
29
|
#
|
30
|
-
# We only support indirectly modifying through associations which
|
30
|
+
# We only support indirectly modifying through associations which have a belongs_to source.
|
31
31
|
# This is the "has_many :tags, through: :taggings" situation, where the join model
|
32
32
|
# typically has a belongs_to on both side. In other words, associations which could also
|
33
33
|
# be represented as has_and_belongs_to_many associations.
|
@@ -76,13 +76,21 @@ module ActiveRecord
|
|
76
76
|
|
77
77
|
def ensure_mutable
|
78
78
|
unless source_reflection.belongs_to?
|
79
|
-
|
79
|
+
if reflection.has_one?
|
80
|
+
raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
81
|
+
else
|
82
|
+
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
83
|
+
end
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
83
87
|
def ensure_not_nested
|
84
88
|
if reflection.nested?
|
85
|
-
|
89
|
+
if reflection.has_one?
|
90
|
+
raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
|
91
|
+
else
|
92
|
+
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
|
93
|
+
end
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
@@ -5,8 +5,8 @@ module ActiveRecord
|
|
5
5
|
FromDatabase.new(name, value, type)
|
6
6
|
end
|
7
7
|
|
8
|
-
def from_user(name, value, type)
|
9
|
-
FromUser.new(name, value, type)
|
8
|
+
def from_user(name, value, type, original_attribute = nil)
|
9
|
+
FromUser.new(name, value, type, original_attribute)
|
10
10
|
end
|
11
11
|
|
12
12
|
def with_cast_value(name, value, type)
|
@@ -26,36 +26,46 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
# This method should not be called directly.
|
28
28
|
# Use #from_database or #from_user
|
29
|
-
def initialize(name, value_before_type_cast, type)
|
29
|
+
def initialize(name, value_before_type_cast, type, original_attribute = nil)
|
30
30
|
@name = name
|
31
31
|
@value_before_type_cast = value_before_type_cast
|
32
32
|
@type = type
|
33
|
+
@original_attribute = original_attribute
|
33
34
|
end
|
34
35
|
|
35
36
|
def value
|
36
37
|
# `defined?` is cheaper than `||=` when we get back falsy values
|
37
|
-
@value =
|
38
|
+
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
38
39
|
@value
|
39
40
|
end
|
40
41
|
|
41
42
|
def original_value
|
42
|
-
|
43
|
+
if assigned?
|
44
|
+
original_attribute.original_value
|
45
|
+
else
|
46
|
+
type_cast(value_before_type_cast)
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
def value_for_database
|
46
|
-
type.
|
51
|
+
type.serialize(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed?
|
55
|
+
changed_from_assignment? || changed_in_place?
|
47
56
|
end
|
48
57
|
|
49
|
-
def
|
50
|
-
type.
|
58
|
+
def changed_in_place?
|
59
|
+
has_been_read? && type.changed_in_place?(original_value_for_database, value)
|
51
60
|
end
|
52
61
|
|
53
|
-
def
|
54
|
-
|
62
|
+
def forgetting_assignment
|
63
|
+
with_value_from_database(value_for_database)
|
55
64
|
end
|
56
65
|
|
57
66
|
def with_value_from_user(value)
|
58
|
-
|
67
|
+
type.assert_valid_value(value)
|
68
|
+
self.class.from_user(name, value, type, self)
|
59
69
|
end
|
60
70
|
|
61
71
|
def with_value_from_database(value)
|
@@ -66,6 +76,10 @@ module ActiveRecord
|
|
66
76
|
self.class.with_cast_value(name, value, type)
|
67
77
|
end
|
68
78
|
|
79
|
+
def with_type(type)
|
80
|
+
self.class.new(name, value_before_type_cast, type, original_attribute)
|
81
|
+
end
|
82
|
+
|
69
83
|
def type_cast(*)
|
70
84
|
raise NotImplementedError
|
71
85
|
end
|
@@ -78,36 +92,62 @@ module ActiveRecord
|
|
78
92
|
false
|
79
93
|
end
|
80
94
|
|
95
|
+
def has_been_read?
|
96
|
+
defined?(@value)
|
97
|
+
end
|
98
|
+
|
81
99
|
def ==(other)
|
82
100
|
self.class == other.class &&
|
83
101
|
name == other.name &&
|
84
102
|
value_before_type_cast == other.value_before_type_cast &&
|
85
103
|
type == other.type
|
86
104
|
end
|
105
|
+
alias eql? ==
|
106
|
+
|
107
|
+
def hash
|
108
|
+
[self.class, name, value_before_type_cast, type].hash
|
109
|
+
end
|
87
110
|
|
88
111
|
protected
|
89
112
|
|
113
|
+
attr_reader :original_attribute
|
114
|
+
alias_method :assigned?, :original_attribute
|
115
|
+
|
90
116
|
def initialize_dup(other)
|
91
117
|
if defined?(@value) && @value.duplicable?
|
92
118
|
@value = @value.dup
|
93
119
|
end
|
94
120
|
end
|
95
121
|
|
96
|
-
|
122
|
+
def changed_from_assignment?
|
123
|
+
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
124
|
+
end
|
125
|
+
|
126
|
+
def original_value_for_database
|
127
|
+
if assigned?
|
128
|
+
original_attribute.original_value_for_database
|
129
|
+
else
|
130
|
+
_original_value_for_database
|
131
|
+
end
|
132
|
+
end
|
97
133
|
|
98
|
-
def
|
99
|
-
|
134
|
+
def _original_value_for_database
|
135
|
+
value_for_database
|
100
136
|
end
|
101
137
|
|
102
138
|
class FromDatabase < Attribute # :nodoc:
|
103
139
|
def type_cast(value)
|
104
|
-
type.
|
140
|
+
type.deserialize(value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def _original_value_for_database
|
144
|
+
value_before_type_cast
|
105
145
|
end
|
106
146
|
end
|
107
147
|
|
108
148
|
class FromUser < Attribute # :nodoc:
|
109
149
|
def type_cast(value)
|
110
|
-
type.
|
150
|
+
type.cast(value)
|
111
151
|
end
|
112
152
|
|
113
153
|
def came_from_user?
|
@@ -130,10 +170,14 @@ module ActiveRecord
|
|
130
170
|
super(name, nil, Type::Value.new)
|
131
171
|
end
|
132
172
|
|
133
|
-
def
|
173
|
+
def type_cast(*)
|
134
174
|
nil
|
135
175
|
end
|
136
176
|
|
177
|
+
def with_type(type)
|
178
|
+
self.class.with_cast_value(name, nil, type)
|
179
|
+
end
|
180
|
+
|
137
181
|
def with_value_from_database(value)
|
138
182
|
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
139
183
|
end
|
@@ -141,6 +185,8 @@ module ActiveRecord
|
|
141
185
|
end
|
142
186
|
|
143
187
|
class Uninitialized < Attribute # :nodoc:
|
188
|
+
UNINITIALIZED_ORIGINAL_VALUE = Object.new
|
189
|
+
|
144
190
|
def initialize(name, type)
|
145
191
|
super(name, nil, type)
|
146
192
|
end
|
@@ -151,6 +197,10 @@ module ActiveRecord
|
|
151
197
|
end
|
152
198
|
end
|
153
199
|
|
200
|
+
def original_value
|
201
|
+
UNINITIALIZED_ORIGINAL_VALUE
|
202
|
+
end
|
203
|
+
|
154
204
|
def value_for_database
|
155
205
|
end
|
156
206
|
|
@@ -158,6 +208,6 @@ module ActiveRecord
|
|
158
208
|
false
|
159
209
|
end
|
160
210
|
end
|
161
|
-
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
162
212
|
end
|
163
213
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_record/attribute'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Attribute # :nodoc:
|
5
|
+
class UserProvidedDefault < FromUser # :nodoc:
|
6
|
+
def initialize(name, value, type, database_default)
|
7
|
+
@user_provided_value = value
|
8
|
+
super(name, value, type, database_default)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value_before_type_cast
|
12
|
+
if user_provided_value.is_a?(Proc)
|
13
|
+
@memoized_value_before_type_cast ||= user_provided_value.call
|
14
|
+
else
|
15
|
+
@user_provided_value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_type(type)
|
20
|
+
self.class.new(name, user_provided_value, type, original_attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :user_provided_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection'
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeAssignment
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::
|
6
|
+
include ActiveModel::AttributeAssignment
|
7
7
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
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?
|
8
|
+
# Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
|
9
|
+
def attributes=(attributes)
|
10
|
+
assign_attributes(attributes)
|
11
|
+
end
|
28
12
|
|
29
|
-
|
30
|
-
multi_parameter_attributes = []
|
31
|
-
nested_parameter_attributes = []
|
13
|
+
private
|
32
14
|
|
33
|
-
|
15
|
+
def _assign_attributes(attributes) # :nodoc:
|
16
|
+
multi_parameter_attributes = {}
|
17
|
+
nested_parameter_attributes = {}
|
34
18
|
|
35
19
|
attributes.each do |k, v|
|
36
20
|
if k.include?("(")
|
37
|
-
multi_parameter_attributes
|
21
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
38
22
|
elsif v.is_a?(Hash)
|
39
|
-
nested_parameter_attributes
|
40
|
-
else
|
41
|
-
_assign_attribute(k, v)
|
23
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
42
24
|
end
|
43
25
|
end
|
26
|
+
super(attributes)
|
44
27
|
|
45
28
|
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
46
29
|
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
47
30
|
end
|
48
31
|
|
49
|
-
alias attributes= assign_attributes
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def _assign_attribute(k, v)
|
54
|
-
public_send("#{k}=", v)
|
55
|
-
rescue NoMethodError, NameError
|
56
|
-
if respond_to?("#{k}=")
|
57
|
-
raise
|
58
|
-
else
|
59
|
-
raise UnknownAttributeError.new(self, k)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
32
|
# Assign any deferred nested attributes after the base attributes have been set.
|
64
33
|
def assign_nested_parameter_attributes(pairs)
|
65
34
|
pairs.each { |k, v| _assign_attribute(k, v) }
|
@@ -81,13 +50,18 @@ module ActiveRecord
|
|
81
50
|
errors = []
|
82
51
|
callstack.each do |name, values_with_empty_parameters|
|
83
52
|
begin
|
84
|
-
|
53
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
54
|
+
values = nil
|
55
|
+
else
|
56
|
+
values = values_with_empty_parameters
|
57
|
+
end
|
58
|
+
send("#{name}=", values)
|
85
59
|
rescue => ex
|
86
60
|
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
87
61
|
end
|
88
62
|
end
|
89
63
|
unless errors.empty?
|
90
|
-
error_descriptions = errors.map
|
64
|
+
error_descriptions = errors.map(&:message).join(",")
|
91
65
|
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
92
66
|
end
|
93
67
|
end
|
@@ -113,100 +87,5 @@ module ActiveRecord
|
|
113
87
|
def find_parameter_position(multiparameter_name)
|
114
88
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
115
89
|
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
|
124
|
-
end
|
125
|
-
|
126
|
-
def read_value
|
127
|
-
return if values.values.compact.empty?
|
128
|
-
|
129
|
-
@cast_type = object.type_for_attribute(name)
|
130
|
-
klass = cast_type.klass
|
131
|
-
|
132
|
-
if klass == Time
|
133
|
-
read_time
|
134
|
-
elsif klass == Date
|
135
|
-
read_date
|
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
|
149
|
-
end
|
150
|
-
|
151
|
-
def read_time
|
152
|
-
# If column is a :time (and not :date or :datetime) there is no need to validate if
|
153
|
-
# there are year/month/day fields
|
154
|
-
if cast_type.type == :time
|
155
|
-
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
156
|
-
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
157
|
-
values[key] ||= value
|
158
|
-
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
|
-
end
|
166
|
-
|
167
|
-
max_position = extract_max_param(6)
|
168
|
-
set_values = values.values_at(*(1..max_position))
|
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
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def read_other
|
185
|
-
max_position = extract_max_param
|
186
|
-
positions = (1..max_position)
|
187
|
-
validate_required_parameters!(positions)
|
188
|
-
|
189
|
-
values.slice(*positions)
|
190
|
-
end
|
191
|
-
|
192
|
-
# Checks whether some blank date parameter exists. Note that this is different
|
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? }
|
198
|
-
end
|
199
|
-
|
200
|
-
# If some position is not provided, it errors out a missing parameter exception.
|
201
|
-
def validate_required_parameters!(positions)
|
202
|
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
203
|
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def extract_max_param(upper_cap = 100)
|
208
|
-
[values.keys.max, upper_cap].min
|
209
|
-
end
|
210
|
-
end
|
211
90
|
end
|
212
91
|
end
|