activerecord 4.2.11.3 → 5.0.7.2
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 +1638 -1132
- 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 +7 -2
- data/lib/active_record/aggregations.rb +34 -21
- data/lib/active_record/association_relation.rb +7 -4
- data/lib/active_record/associations.rb +347 -218
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +22 -10
- data/lib/active_record/associations/association_scope.rb +75 -104
- 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 +16 -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 +13 -11
- data/lib/active_record/associations/collection_association.rb +85 -69
- data/lib/active_record/associations/collection_proxy.rb +104 -46
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +21 -78
- data/lib/active_record/associations/has_many_through_association.rb +6 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +38 -22
- data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +52 -71
- data/lib/active_record/associations/preloader/collection_association.rb +0 -7
- 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/singular_association.rb +0 -1
- data/lib/active_record/associations/preloader/through_association.rb +36 -17
- data/lib/active_record/associations/singular_association.rb +13 -1
- data/lib/active_record/associations/through_association.rb +12 -4
- data/lib/active_record/attribute.rb +69 -19
- 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 +69 -44
- 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 +16 -3
- 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 +32 -3
- data/lib/active_record/attribute_set/builder.rb +42 -16
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +54 -17
- 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 +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
- 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 -188
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
- 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 +108 -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 +25 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -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 +37 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
- 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 +13 -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 +56 -19
- 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 +250 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
- 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 +151 -194
- 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 +92 -108
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +116 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain.rb +20 -9
- 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 +77 -41
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +17 -14
- 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 +48 -24
- data/lib/active_record/migration.rb +362 -111
- 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 +270 -73
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +152 -90
- data/lib/active_record/query_cache.rb +18 -23
- data/lib/active_record/querying.rb +12 -11
- 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 +52 -41
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +302 -115
- data/lib/active_record/relation.rb +187 -120
- data/lib/active_record/relation/batches.rb +141 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +92 -117
- data/lib/active_record/relation/delegation.rb +8 -20
- data/lib/active_record/relation/finder_methods.rb +173 -89
- 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 +11 -4
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +105 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +54 -37
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +34 -16
- data/lib/active_record/scoping/default.rb +28 -10
- data/lib/active_record/scoping/named.rb +59 -26
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +3 -5
- data/lib/active_record/statement_cache.rb +17 -15
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +69 -0
- data/lib/active_record/tasks/database_tasks.rb +66 -49
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
- 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 +63 -0
- data/lib/active_record/transactions.rb +139 -57
- 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 +33 -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 +33 -33
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
- 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 +33 -16
- 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 +58 -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
@@ -3,33 +3,30 @@ require 'active_record/attribute'
|
|
3
3
|
module ActiveRecord
|
4
4
|
class AttributeSet # :nodoc:
|
5
5
|
class Builder # :nodoc:
|
6
|
-
attr_reader :types, :
|
6
|
+
attr_reader :types, :default_attributes
|
7
7
|
|
8
|
-
def initialize(types,
|
8
|
+
def initialize(types, default_attributes = {})
|
9
9
|
@types = types
|
10
|
-
@
|
10
|
+
@default_attributes = default_attributes
|
11
11
|
end
|
12
12
|
|
13
13
|
def build_from_database(values = {}, additional_types = {})
|
14
|
-
|
15
|
-
values[always_initialized] = nil
|
16
|
-
end
|
17
|
-
|
18
|
-
attributes = LazyAttributeHash.new(types, values, additional_types)
|
14
|
+
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
19
15
|
AttributeSet.new(attributes)
|
20
16
|
end
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
24
20
|
class LazyAttributeHash # :nodoc:
|
25
|
-
delegate :transform_values, to: :materialize
|
21
|
+
delegate :transform_values, :each_key, :fetch, :except, to: :materialize
|
26
22
|
|
27
|
-
def initialize(types, values, additional_types)
|
23
|
+
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
28
24
|
@types = types
|
29
25
|
@values = values
|
30
26
|
@additional_types = additional_types
|
31
27
|
@materialized = false
|
32
|
-
@delegate_hash =
|
28
|
+
@delegate_hash = delegate_hash
|
29
|
+
@default_attributes = default_attributes
|
33
30
|
end
|
34
31
|
|
35
32
|
def key?(key)
|
@@ -47,12 +44,14 @@ module ActiveRecord
|
|
47
44
|
delegate_hash[key] = value
|
48
45
|
end
|
49
46
|
|
50
|
-
def
|
51
|
-
|
47
|
+
def deep_dup
|
48
|
+
dup.tap do |copy|
|
49
|
+
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
50
|
+
end
|
52
51
|
end
|
53
52
|
|
54
53
|
def initialize_dup(_)
|
55
|
-
@delegate_hash = delegate_hash
|
54
|
+
@delegate_hash = Hash[delegate_hash]
|
56
55
|
super
|
57
56
|
end
|
58
57
|
|
@@ -74,9 +73,31 @@ module ActiveRecord
|
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
76
|
+
def marshal_dump
|
77
|
+
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
|
78
|
+
end
|
79
|
+
|
80
|
+
def marshal_load(values)
|
81
|
+
if values.is_a?(Hash)
|
82
|
+
empty_hash = {}.freeze
|
83
|
+
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
84
|
+
@materialized = true
|
85
|
+
else
|
86
|
+
initialize(*values)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode_with(coder)
|
91
|
+
coder["delegate_hash"] = materialize
|
92
|
+
end
|
93
|
+
|
94
|
+
def init_with(coder)
|
95
|
+
marshal_load(coder["delegate_hash"])
|
96
|
+
end
|
97
|
+
|
77
98
|
protected
|
78
99
|
|
79
|
-
attr_reader :types, :values, :additional_types, :delegate_hash
|
100
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
80
101
|
|
81
102
|
def materialize
|
82
103
|
unless @materialized
|
@@ -99,7 +120,12 @@ module ActiveRecord
|
|
99
120
|
if value_present
|
100
121
|
delegate_hash[name] = Attribute.from_database(name, value, type)
|
101
122
|
elsif types.key?(name)
|
102
|
-
|
123
|
+
attr = default_attributes[name]
|
124
|
+
if attr
|
125
|
+
delegate_hash[name] = attr.dup
|
126
|
+
else
|
127
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
128
|
+
end
|
103
129
|
end
|
104
130
|
end
|
105
131
|
end
|
@@ -1,33 +1,44 @@
|
|
1
|
+
require 'active_record/attribute/user_provided_default'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
|
-
|
4
|
+
# See ActiveRecord::Attributes::ClassMethods for documentation
|
5
|
+
module Attributes
|
3
6
|
extend ActiveSupport::Concern
|
4
7
|
|
5
|
-
Type = ActiveRecord::Type
|
6
|
-
|
7
8
|
included do
|
8
|
-
class_attribute :
|
9
|
-
|
10
|
-
self.user_provided_columns = {}
|
11
|
-
self.user_provided_defaults = {}
|
12
|
-
|
13
|
-
delegate :persistable_attribute_names, to: :class
|
9
|
+
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
|
10
|
+
self.attributes_to_define_after_schema_loads = {}
|
14
11
|
end
|
15
12
|
|
16
|
-
module ClassMethods
|
17
|
-
# Defines
|
18
|
-
#
|
19
|
-
#
|
13
|
+
module ClassMethods
|
14
|
+
# Defines an attribute with a type on this model. It will override the
|
15
|
+
# type of existing attributes if needed. This allows control over how
|
16
|
+
# values are converted to and from SQL when assigned to a model. It also
|
17
|
+
# changes the behavior of values passed to
|
18
|
+
# {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
|
19
|
+
# your domain objects across much of Active Record, without having to
|
20
|
+
# rely on implementation details or monkey patching.
|
20
21
|
#
|
21
|
-
# +name+ The name of the methods to define attribute methods for, and the
|
22
|
-
# this will persist to.
|
22
|
+
# +name+ The name of the methods to define attribute methods for, and the
|
23
|
+
# column which this will persist to.
|
23
24
|
#
|
24
|
-
# +cast_type+ A
|
25
|
-
# See the examples
|
25
|
+
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
|
26
|
+
# to be used for this attribute. See the examples below for more
|
27
|
+
# information about providing custom type objects.
|
26
28
|
#
|
27
29
|
# ==== Options
|
28
|
-
# The options hash accepts the following options:
|
29
30
|
#
|
30
|
-
#
|
31
|
+
# The following options are accepted:
|
32
|
+
#
|
33
|
+
# +default+ The default value to use when no value is provided. If this option
|
34
|
+
# is not passed, the previous default value (if any) will be used.
|
35
|
+
# Otherwise, the default will be +nil+.
|
36
|
+
#
|
37
|
+
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
|
38
|
+
# examples below).
|
39
|
+
#
|
40
|
+
# +range+ (PostgreSQL only) specifies that the type should be a range (see the
|
41
|
+
# examples below).
|
31
42
|
#
|
32
43
|
# ==== Examples
|
33
44
|
#
|
@@ -48,99 +59,206 @@ module ActiveRecord
|
|
48
59
|
# store_listing.price_in_cents # => BigDecimal.new(10.1)
|
49
60
|
#
|
50
61
|
# class StoreListing < ActiveRecord::Base
|
51
|
-
# attribute :price_in_cents,
|
62
|
+
# attribute :price_in_cents, :integer
|
52
63
|
# end
|
53
64
|
#
|
54
65
|
# # after
|
55
66
|
# store_listing.price_in_cents # => 10
|
56
67
|
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
68
|
+
# A default can also be provided.
|
69
|
+
#
|
70
|
+
# # db/schema.rb
|
71
|
+
# create_table :store_listings, force: true do |t|
|
72
|
+
# t.string :my_string, default: "original default"
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# StoreListing.new.my_string # => "original default"
|
76
|
+
#
|
77
|
+
# # app/models/store_listing.rb
|
78
|
+
# class StoreListing < ActiveRecord::Base
|
79
|
+
# attribute :my_string, :string, default: "new default"
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# StoreListing.new.my_string # => "new default"
|
83
|
+
#
|
84
|
+
# class Product < ActiveRecord::Base
|
85
|
+
# attribute :my_default_proc, :datetime, default: -> { Time.now }
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
|
89
|
+
# sleep 1
|
90
|
+
# Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
|
91
|
+
#
|
92
|
+
# \Attributes do not need to be backed by a database column.
|
93
|
+
#
|
94
|
+
# # app/models/my_model.rb
|
95
|
+
# class MyModel < ActiveRecord::Base
|
96
|
+
# attribute :my_string, :string
|
97
|
+
# attribute :my_int_array, :integer, array: true
|
98
|
+
# attribute :my_float_range, :float, range: true
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# model = MyModel.new(
|
102
|
+
# my_string: "string",
|
103
|
+
# my_int_array: ["1", "2", "3"],
|
104
|
+
# my_float_range: "[1,3.5]",
|
105
|
+
# )
|
106
|
+
# model.attributes
|
107
|
+
# # =>
|
108
|
+
# {
|
109
|
+
# my_string: "string",
|
110
|
+
# my_int_array: [1, 2, 3],
|
111
|
+
# my_float_range: 1.0..3.5
|
112
|
+
# }
|
113
|
+
#
|
114
|
+
# ==== Creating Custom Types
|
115
|
+
#
|
116
|
+
# Users may also define their own custom types, as long as they respond
|
117
|
+
# to the methods defined on the value type. The method +deserialize+ or
|
118
|
+
# +cast+ will be called on your type object, with raw input from the
|
119
|
+
# database or from your controllers. See ActiveRecord::Type::Value for the
|
120
|
+
# expected API. It is recommended that your type objects inherit from an
|
121
|
+
# existing type, or from ActiveRecord::Type::Value
|
62
122
|
#
|
63
123
|
# class MoneyType < ActiveRecord::Type::Integer
|
64
|
-
# def
|
65
|
-
# if value.include?('$')
|
124
|
+
# def cast(value)
|
125
|
+
# if !value.kind_of?(Numeric) && value.include?('$')
|
66
126
|
# price_in_dollars = value.gsub(/\$/, '').to_f
|
67
|
-
# price_in_dollars * 100
|
127
|
+
# super(price_in_dollars * 100)
|
68
128
|
# else
|
69
|
-
#
|
129
|
+
# super
|
70
130
|
# end
|
71
131
|
# end
|
72
132
|
# end
|
73
133
|
#
|
134
|
+
# # config/initializers/types.rb
|
135
|
+
# ActiveRecord::Type.register(:money, MoneyType)
|
136
|
+
#
|
137
|
+
# # app/models/store_listing.rb
|
74
138
|
# class StoreListing < ActiveRecord::Base
|
75
|
-
# attribute :price_in_cents,
|
139
|
+
# attribute :price_in_cents, :money
|
76
140
|
# end
|
77
141
|
#
|
78
142
|
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
79
143
|
# store_listing.price_in_cents # => 1000
|
80
|
-
|
144
|
+
#
|
145
|
+
# For more details on creating custom types, see the documentation for
|
146
|
+
# ActiveRecord::Type::Value. For more details on registering your types
|
147
|
+
# to be referenced by a symbol, see ActiveRecord::Type.register. You can
|
148
|
+
# also pass a type object directly, in place of a symbol.
|
149
|
+
#
|
150
|
+
# ==== \Querying
|
151
|
+
#
|
152
|
+
# When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
|
153
|
+
# use the type defined by the model class to convert the value to SQL,
|
154
|
+
# calling +serialize+ on your type object. For example:
|
155
|
+
#
|
156
|
+
# class Money < Struct.new(:amount, :currency)
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# class MoneyType < Type::Value
|
160
|
+
# def initialize(currency_converter:)
|
161
|
+
# @currency_converter = currency_converter
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# # value will be the result of +deserialize+ or
|
165
|
+
# # +cast+. Assumed to be an instance of +Money+ in
|
166
|
+
# # this case.
|
167
|
+
# def serialize(value)
|
168
|
+
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
169
|
+
# value_in_bitcoins.amount
|
170
|
+
# end
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# # config/initializers/types.rb
|
174
|
+
# ActiveRecord::Type.register(:money, MoneyType)
|
175
|
+
#
|
176
|
+
# # app/models/product.rb
|
177
|
+
# class Product < ActiveRecord::Base
|
178
|
+
# currency_converter = ConversionRatesFromTheInternet.new
|
179
|
+
# attribute :price_in_bitcoins, :money, currency_converter: currency_converter
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
183
|
+
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
|
184
|
+
#
|
185
|
+
# Product.where(price_in_bitcoins: Money.new(5, "GBP"))
|
186
|
+
# # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
|
187
|
+
#
|
188
|
+
# ==== Dirty Tracking
|
189
|
+
#
|
190
|
+
# The type of an attribute is given the opportunity to change how dirty
|
191
|
+
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
192
|
+
# will be called from ActiveModel::Dirty. See the documentation for those
|
193
|
+
# methods in ActiveRecord::Type::Value for more details.
|
194
|
+
def attribute(name, cast_type, **options)
|
81
195
|
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)
|
196
|
+
reload_schema_from_cache
|
85
197
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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))
|
198
|
+
self.attributes_to_define_after_schema_loads =
|
199
|
+
attributes_to_define_after_schema_loads.merge(
|
200
|
+
name => [cast_type, options]
|
201
|
+
)
|
94
202
|
end
|
95
203
|
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
204
|
+
# This is the low level API which sits beneath +attribute+. It only
|
205
|
+
# accepts type objects, and will do its work immediately instead of
|
206
|
+
# waiting for the schema to load. Automatic schema detection and
|
207
|
+
# ClassMethods#attribute both call this under the hood. While this method
|
208
|
+
# is provided so it can be used by plugin authors, application code
|
209
|
+
# should probably use ClassMethods#attribute.
|
210
|
+
#
|
211
|
+
# +name+ The name of the attribute being defined. Expected to be a +String+.
|
212
|
+
#
|
213
|
+
# +cast_type+ The type object to use for this attribute.
|
214
|
+
#
|
215
|
+
# +default+ The default value to use when no value is provided. If this option
|
216
|
+
# is not passed, the previous default value (if any) will be used.
|
217
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
218
|
+
# will be called once each time a new value is needed.
|
219
|
+
#
|
220
|
+
# +user_provided_default+ Whether the default value should be cast using
|
221
|
+
# +cast+ or +deserialize+.
|
222
|
+
def define_attribute(
|
223
|
+
name,
|
224
|
+
cast_type,
|
225
|
+
default: NO_DEFAULT_PROVIDED,
|
226
|
+
user_provided_default: true
|
227
|
+
)
|
228
|
+
attribute_types[name] = cast_type
|
229
|
+
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
103
230
|
end
|
104
231
|
|
105
|
-
def
|
232
|
+
def load_schema! # :nodoc:
|
106
233
|
super
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
def add_user_provided_columns(schema_columns)
|
113
|
-
existing_columns = schema_columns.map do |column|
|
114
|
-
new_type = user_provided_columns[column.name]
|
115
|
-
if new_type
|
116
|
-
column.with_type(new_type)
|
117
|
-
else
|
118
|
-
column
|
234
|
+
attributes_to_define_after_schema_loads.each do |name, (type, options)|
|
235
|
+
if type.is_a?(Symbol)
|
236
|
+
type = ActiveRecord::Type.lookup(type, **options.except(:default))
|
119
237
|
end
|
120
|
-
end
|
121
238
|
|
122
|
-
|
123
|
-
new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
|
124
|
-
connection.new_column(name, nil, type)
|
239
|
+
define_attribute(name, type, **options.slice(:default))
|
125
240
|
end
|
126
|
-
|
127
|
-
existing_columns + new_columns
|
128
241
|
end
|
129
242
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
243
|
+
private
|
244
|
+
|
245
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
246
|
+
private_constant :NO_DEFAULT_PROVIDED
|
141
247
|
|
142
|
-
def
|
143
|
-
|
248
|
+
def define_default_attribute(name, value, type, from_user:)
|
249
|
+
if value == NO_DEFAULT_PROVIDED
|
250
|
+
default_attribute = _default_attributes[name].with_type(type)
|
251
|
+
elsif from_user
|
252
|
+
default_attribute = Attribute::UserProvidedDefault.new(
|
253
|
+
name,
|
254
|
+
value,
|
255
|
+
type,
|
256
|
+
_default_attributes.fetch(name.to_s) { nil },
|
257
|
+
)
|
258
|
+
else
|
259
|
+
default_attribute = Attribute.from_database(name, value, type)
|
260
|
+
end
|
261
|
+
_default_attributes[name] = default_attribute
|
144
262
|
end
|
145
263
|
end
|
146
264
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
# = Active Record Autosave Association
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# AutosaveAssociation is a module that takes care of automatically saving
|
5
5
|
# associated records when their parent is saved. In addition to saving, it
|
6
6
|
# also destroys any associated records that were marked for destruction.
|
7
|
-
# (See
|
7
|
+
# (See #mark_for_destruction and #marked_for_destruction?).
|
8
8
|
#
|
9
9
|
# Saving of the parent, its associations, and the destruction of marked
|
10
10
|
# associations, all happen inside a transaction. This should never leave the
|
@@ -22,7 +22,7 @@ module ActiveRecord
|
|
22
22
|
#
|
23
23
|
# == Validation
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# Child records are validated unless <tt>:validate</tt> is +false+.
|
26
26
|
#
|
27
27
|
# == Callbacks
|
28
28
|
#
|
@@ -125,7 +125,6 @@ module ActiveRecord
|
|
125
125
|
# Now it _is_ removed from the database:
|
126
126
|
#
|
127
127
|
# Comment.find_by(id: id).nil? # => true
|
128
|
-
|
129
128
|
module AutosaveAssociation
|
130
129
|
extend ActiveSupport::Concern
|
131
130
|
|
@@ -141,9 +140,11 @@ module ActiveRecord
|
|
141
140
|
|
142
141
|
included do
|
143
142
|
Associations::Builder::Association.extensions << AssociationBuilderExtension
|
143
|
+
mattr_accessor :index_nested_attribute_errors, instance_writer: false
|
144
|
+
self.index_nested_attribute_errors = false
|
144
145
|
end
|
145
146
|
|
146
|
-
module ClassMethods
|
147
|
+
module ClassMethods # :nodoc:
|
147
148
|
private
|
148
149
|
|
149
150
|
def define_non_cyclic_method(name, &block)
|
@@ -198,7 +199,7 @@ module ActiveRecord
|
|
198
199
|
after_create save_method
|
199
200
|
after_update save_method
|
200
201
|
else
|
201
|
-
define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
|
202
|
+
define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
|
202
203
|
before_save save_method
|
203
204
|
end
|
204
205
|
|
@@ -214,8 +215,15 @@ module ActiveRecord
|
|
214
215
|
method = :validate_single_association
|
215
216
|
end
|
216
217
|
|
217
|
-
define_non_cyclic_method(validation_method)
|
218
|
+
define_non_cyclic_method(validation_method) do
|
219
|
+
send(method, reflection)
|
220
|
+
# TODO: remove the following line as soon as the return value of
|
221
|
+
# callbacks is ignored, that is, returning `false` does not
|
222
|
+
# display a deprecation warning or halts the callback chain.
|
223
|
+
true
|
224
|
+
end
|
218
225
|
validate validation_method
|
226
|
+
after_validation :_ensure_no_duplicate_errors
|
219
227
|
end
|
220
228
|
end
|
221
229
|
end
|
@@ -227,7 +235,7 @@ module ActiveRecord
|
|
227
235
|
super
|
228
236
|
end
|
229
237
|
|
230
|
-
# Marks this record to be destroyed as part of the
|
238
|
+
# Marks this record to be destroyed as part of the parent's save transaction.
|
231
239
|
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
|
232
240
|
# when <tt>parent.save</tt> is called.
|
233
241
|
#
|
@@ -236,7 +244,7 @@ module ActiveRecord
|
|
236
244
|
@marked_for_destruction = true
|
237
245
|
end
|
238
246
|
|
239
|
-
# Returns whether or not this record will be destroyed as part of the
|
247
|
+
# Returns whether or not this record will be destroyed as part of the parent's save transaction.
|
240
248
|
#
|
241
249
|
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
242
250
|
def marked_for_destruction?
|
@@ -271,9 +279,9 @@ module ActiveRecord
|
|
271
279
|
if new_record
|
272
280
|
association && association.target
|
273
281
|
elsif autosave
|
274
|
-
association.target.find_all
|
282
|
+
association.target.find_all(&:changed_for_autosave?)
|
275
283
|
else
|
276
|
-
association.target.find_all
|
284
|
+
association.target.find_all(&:new_record?)
|
277
285
|
end
|
278
286
|
end
|
279
287
|
|
@@ -309,7 +317,7 @@ module ActiveRecord
|
|
309
317
|
def validate_collection_association(reflection)
|
310
318
|
if association = association_instance_get(reflection.name)
|
311
319
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
312
|
-
records.
|
320
|
+
records.each_with_index { |record, index| association_valid?(reflection, record, index) }
|
313
321
|
end
|
314
322
|
end
|
315
323
|
end
|
@@ -317,17 +325,30 @@ module ActiveRecord
|
|
317
325
|
# Returns whether or not the association is valid and applies any errors to
|
318
326
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
319
327
|
# enabled records if they're marked_for_destruction? or destroyed.
|
320
|
-
def association_valid?(reflection, record)
|
328
|
+
def association_valid?(reflection, record, index=nil)
|
321
329
|
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
|
322
330
|
|
323
331
|
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
|
332
|
+
|
324
333
|
unless valid = record.valid?(validation_context)
|
325
334
|
if reflection.options[:autosave]
|
335
|
+
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
|
336
|
+
|
326
337
|
record.errors.each do |attribute, message|
|
327
|
-
attribute =
|
338
|
+
attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
328
339
|
errors[attribute] << message
|
329
340
|
errors[attribute].uniq!
|
330
341
|
end
|
342
|
+
|
343
|
+
record.errors.details.each_key do |attribute|
|
344
|
+
reflection_attribute =
|
345
|
+
normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
|
346
|
+
|
347
|
+
record.errors.details[attribute].each do |error|
|
348
|
+
errors.details[reflection_attribute] << error
|
349
|
+
errors.details[reflection_attribute].uniq!
|
350
|
+
end
|
351
|
+
end
|
331
352
|
else
|
332
353
|
errors.add(reflection.name)
|
333
354
|
end
|
@@ -335,6 +356,14 @@ module ActiveRecord
|
|
335
356
|
valid
|
336
357
|
end
|
337
358
|
|
359
|
+
def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
360
|
+
if indexed_attribute
|
361
|
+
"#{reflection.name}[#{index}].#{attribute}"
|
362
|
+
else
|
363
|
+
"#{reflection.name}.#{attribute}"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
338
367
|
# Is used as a before_save callback to check while saving a collection
|
339
368
|
# association whether or not the parent was a new record before saving.
|
340
369
|
def before_save_collection_association
|
@@ -346,7 +375,7 @@ module ActiveRecord
|
|
346
375
|
# <tt>:autosave</tt> is enabled on the association.
|
347
376
|
#
|
348
377
|
# In addition, it destroys all children that were marked for destruction
|
349
|
-
# with mark_for_destruction.
|
378
|
+
# with #mark_for_destruction.
|
350
379
|
#
|
351
380
|
# This all happens inside a transaction, _if_ the Transactions module is included into
|
352
381
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
@@ -389,7 +418,7 @@ module ActiveRecord
|
|
389
418
|
# on the association.
|
390
419
|
#
|
391
420
|
# In addition, it will destroy the association if it was marked for
|
392
|
-
# destruction with mark_for_destruction.
|
421
|
+
# destruction with #mark_for_destruction.
|
393
422
|
#
|
394
423
|
# This all happens inside a transaction, _if_ the Transactions module is included into
|
395
424
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
@@ -430,7 +459,9 @@ module ActiveRecord
|
|
430
459
|
# In addition, it will destroy the association if it was marked for destruction.
|
431
460
|
def save_belongs_to_association(reflection)
|
432
461
|
association = association_instance_get(reflection.name)
|
433
|
-
|
462
|
+
return unless association && association.loaded? && !association.stale_target?
|
463
|
+
|
464
|
+
record = association.load_target
|
434
465
|
if record && !record.destroyed?
|
435
466
|
autosave = reflection.options[:autosave]
|
436
467
|
|
@@ -450,5 +481,11 @@ module ActiveRecord
|
|
450
481
|
end
|
451
482
|
end
|
452
483
|
end
|
484
|
+
|
485
|
+
def _ensure_no_duplicate_errors
|
486
|
+
errors.messages.each_key do |attribute|
|
487
|
+
errors[attribute].uniq!
|
488
|
+
end
|
489
|
+
end
|
453
490
|
end
|
454
491
|
end
|