activerecord 4.1.15 → 4.2.11.3
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 +1162 -1792
- data/README.rdoc +15 -10
- data/lib/active_record.rb +4 -0
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +83 -38
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +63 -27
- data/lib/active_record/associations/collection_proxy.rb +29 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/preloader.rb +36 -26
- data/lib/active_record/associations/preloader/association.rb +14 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +5 -12
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +19 -11
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +56 -94
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +19 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -39
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +9 -11
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +55 -69
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration.rb +71 -46
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +5 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +46 -26
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +18 -11
- data/lib/active_record/railties/databases.rake +50 -51
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +273 -114
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/finder_methods.rb +70 -47
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder.rb +16 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
- data/lib/active_record/relation/query_methods.rb +114 -65
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +53 -27
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +25 -29
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -26,8 +26,6 @@ module ActiveRecord
|
|
26
26
|
protected
|
27
27
|
|
28
28
|
if Module.methods_transplantable?
|
29
|
-
# See define_method_attribute in read.rb for an explanation of
|
30
|
-
# this code.
|
31
29
|
def define_method_attribute=(name)
|
32
30
|
method = WriterMethodCache[name]
|
33
31
|
generated_attribute_methods.module_eval {
|
@@ -52,14 +50,14 @@ module ActiveRecord
|
|
52
50
|
end
|
53
51
|
|
54
52
|
# Updates the attribute identified by <tt>attr_name</tt> with the
|
55
|
-
# specified +value+. Empty strings for
|
53
|
+
# specified +value+. Empty strings for Integer and Float columns are
|
56
54
|
# turned into +nil+.
|
57
55
|
def write_attribute(attr_name, value)
|
58
|
-
write_attribute_with_type_cast(attr_name, value,
|
56
|
+
write_attribute_with_type_cast(attr_name, value, true)
|
59
57
|
end
|
60
58
|
|
61
59
|
def raw_write_attribute(attr_name, value)
|
62
|
-
write_attribute_with_type_cast(attr_name, value,
|
60
|
+
write_attribute_with_type_cast(attr_name, value, false)
|
63
61
|
end
|
64
62
|
|
65
63
|
private
|
@@ -68,30 +66,17 @@ module ActiveRecord
|
|
68
66
|
write_attribute(attribute_name, value)
|
69
67
|
end
|
70
68
|
|
71
|
-
def
|
72
|
-
return value unless column
|
73
|
-
|
74
|
-
column.type_cast_for_write value
|
75
|
-
end
|
76
|
-
alias_method :raw_type_cast_attribute_for_write, :type_cast_attribute_for_write
|
77
|
-
|
78
|
-
def write_attribute_with_type_cast(attr_name, value, type_cast_method)
|
69
|
+
def write_attribute_with_type_cast(attr_name, value, should_type_cast)
|
79
70
|
attr_name = attr_name.to_s
|
80
71
|
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
81
|
-
@attributes_cache.delete(attr_name)
|
82
|
-
column = column_for_attribute(attr_name)
|
83
72
|
|
84
|
-
|
85
|
-
|
86
|
-
if column && column.binary?
|
87
|
-
@attributes_cache[attr_name] = value
|
88
|
-
end
|
89
|
-
|
90
|
-
if column || @attributes.has_key?(attr_name)
|
91
|
-
@attributes[attr_name] = send(type_cast_method, column, value)
|
73
|
+
if should_type_cast
|
74
|
+
@attributes.write_from_user(attr_name, value)
|
92
75
|
else
|
93
|
-
|
76
|
+
@attributes.write_cast_value(attr_name, value)
|
94
77
|
end
|
78
|
+
|
79
|
+
value
|
95
80
|
end
|
96
81
|
end
|
97
82
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'active_record/attribute_set/builder'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class AttributeSet # :nodoc:
|
5
|
+
def initialize(attributes)
|
6
|
+
@attributes = attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
attributes[name] || Attribute.null(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def values_before_type_cast
|
14
|
+
attributes.transform_values(&:value_before_type_cast)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
initialized_attributes.transform_values(&:value)
|
19
|
+
end
|
20
|
+
alias_method :to_h, :to_hash
|
21
|
+
|
22
|
+
def key?(name)
|
23
|
+
attributes.key?(name) && self[name].initialized?
|
24
|
+
end
|
25
|
+
|
26
|
+
def keys
|
27
|
+
attributes.initialized_keys
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_value(name)
|
31
|
+
self[name].value { |n| yield n if block_given? }
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_from_database(name, value)
|
35
|
+
attributes[name] = self[name].with_value_from_database(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_from_user(name, value)
|
39
|
+
attributes[name] = self[name].with_value_from_user(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_cast_value(name, value)
|
43
|
+
attributes[name] = self[name].with_cast_value(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def freeze
|
47
|
+
@attributes.freeze
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize_dup(_)
|
52
|
+
@attributes = attributes.dup
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize_clone(_)
|
57
|
+
@attributes = attributes.clone
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def reset(key)
|
62
|
+
if key?(key)
|
63
|
+
write_from_database(key, nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ==(other)
|
68
|
+
attributes == other.attributes
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
attr_reader :attributes
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def initialized_attributes
|
78
|
+
attributes.select { |_, attr| attr.initialized? }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'active_record/attribute'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class AttributeSet # :nodoc:
|
5
|
+
class Builder # :nodoc:
|
6
|
+
attr_reader :types, :always_initialized
|
7
|
+
|
8
|
+
def initialize(types, always_initialized = nil)
|
9
|
+
@types = types
|
10
|
+
@always_initialized = always_initialized
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_from_database(values = {}, additional_types = {})
|
14
|
+
if always_initialized && !values.key?(always_initialized)
|
15
|
+
values[always_initialized] = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
attributes = LazyAttributeHash.new(types, values, additional_types)
|
19
|
+
AttributeSet.new(attributes)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class LazyAttributeHash # :nodoc:
|
25
|
+
delegate :transform_values, to: :materialize
|
26
|
+
|
27
|
+
def initialize(types, values, additional_types)
|
28
|
+
@types = types
|
29
|
+
@values = values
|
30
|
+
@additional_types = additional_types
|
31
|
+
@materialized = false
|
32
|
+
@delegate_hash = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def key?(key)
|
36
|
+
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
delegate_hash[key] || assign_default_value(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
if frozen?
|
45
|
+
raise RuntimeError, "Can't modify frozen hash"
|
46
|
+
end
|
47
|
+
delegate_hash[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialized_keys
|
51
|
+
delegate_hash.keys | values.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_dup(_)
|
55
|
+
@delegate_hash = delegate_hash.transform_values(&:dup)
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
def select
|
60
|
+
keys = types.keys | values.keys | delegate_hash.keys
|
61
|
+
keys.each_with_object({}) do |key, hash|
|
62
|
+
attribute = self[key]
|
63
|
+
if yield(key, attribute)
|
64
|
+
hash[key] = attribute
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def ==(other)
|
70
|
+
if other.is_a?(LazyAttributeHash)
|
71
|
+
materialize == other.materialize
|
72
|
+
else
|
73
|
+
materialize == other
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
attr_reader :types, :values, :additional_types, :delegate_hash
|
80
|
+
|
81
|
+
def materialize
|
82
|
+
unless @materialized
|
83
|
+
values.each_key { |key| self[key] }
|
84
|
+
types.each_key { |key| self[key] }
|
85
|
+
unless frozen?
|
86
|
+
@materialized = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
delegate_hash
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def assign_default_value(name)
|
95
|
+
type = additional_types.fetch(name, types[name])
|
96
|
+
value_present = true
|
97
|
+
value = values.fetch(name) { value_present = false }
|
98
|
+
|
99
|
+
if value_present
|
100
|
+
delegate_hash[name] = Attribute.from_database(name, value, type)
|
101
|
+
elsif types.key?(name)
|
102
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Attributes # :nodoc:
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
Type = ActiveRecord::Type
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :user_provided_columns, instance_accessor: false # :internal:
|
9
|
+
class_attribute :user_provided_defaults, instance_accessor: false # :internal:
|
10
|
+
self.user_provided_columns = {}
|
11
|
+
self.user_provided_defaults = {}
|
12
|
+
|
13
|
+
delegate :persistable_attribute_names, to: :class
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods # :nodoc:
|
17
|
+
# Defines or overrides a attribute on this model. This allows customization of
|
18
|
+
# Active Record's type casting behavior, as well as adding support for user defined
|
19
|
+
# types.
|
20
|
+
#
|
21
|
+
# +name+ The name of the methods to define attribute methods for, and the column which
|
22
|
+
# this will persist to.
|
23
|
+
#
|
24
|
+
# +cast_type+ A type object that contains information about how to type cast the value.
|
25
|
+
# See the examples section for more information.
|
26
|
+
#
|
27
|
+
# ==== Options
|
28
|
+
# The options hash accepts the following options:
|
29
|
+
#
|
30
|
+
# +default+ is the default value that the column should use on a new record.
|
31
|
+
#
|
32
|
+
# ==== Examples
|
33
|
+
#
|
34
|
+
# The type detected by Active Record can be overridden.
|
35
|
+
#
|
36
|
+
# # db/schema.rb
|
37
|
+
# create_table :store_listings, force: true do |t|
|
38
|
+
# t.decimal :price_in_cents
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # app/models/store_listing.rb
|
42
|
+
# class StoreListing < ActiveRecord::Base
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# store_listing = StoreListing.new(price_in_cents: '10.1')
|
46
|
+
#
|
47
|
+
# # before
|
48
|
+
# store_listing.price_in_cents # => BigDecimal.new(10.1)
|
49
|
+
#
|
50
|
+
# class StoreListing < ActiveRecord::Base
|
51
|
+
# attribute :price_in_cents, Type::Integer.new
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # after
|
55
|
+
# store_listing.price_in_cents # => 10
|
56
|
+
#
|
57
|
+
# Users may also define their own custom types, as long as they respond to the methods
|
58
|
+
# defined on the value type. The `type_cast` method on your type object will be called
|
59
|
+
# with values both from the database, and from your controllers. See
|
60
|
+
# `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
|
61
|
+
# type objects inherit from an existing type, or the base value type.
|
62
|
+
#
|
63
|
+
# class MoneyType < ActiveRecord::Type::Integer
|
64
|
+
# def type_cast(value)
|
65
|
+
# if value.include?('$')
|
66
|
+
# price_in_dollars = value.gsub(/\$/, '').to_f
|
67
|
+
# price_in_dollars * 100
|
68
|
+
# else
|
69
|
+
# value.to_i
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# class StoreListing < ActiveRecord::Base
|
75
|
+
# attribute :price_in_cents, MoneyType.new
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
79
|
+
# store_listing.price_in_cents # => 1000
|
80
|
+
def attribute(name, cast_type, options = {})
|
81
|
+
name = name.to_s
|
82
|
+
clear_caches_calculated_from_columns
|
83
|
+
# Assign a new hash to ensure that subclasses do not share a hash
|
84
|
+
self.user_provided_columns = user_provided_columns.merge(name => cast_type)
|
85
|
+
|
86
|
+
if options.key?(:default)
|
87
|
+
self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns an array of column objects for the table associated with this class.
|
92
|
+
def columns
|
93
|
+
@columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns a hash of column objects for the table associated with this class.
|
97
|
+
def columns_hash
|
98
|
+
@columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
|
99
|
+
end
|
100
|
+
|
101
|
+
def persistable_attribute_names # :nodoc:
|
102
|
+
@persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
|
103
|
+
end
|
104
|
+
|
105
|
+
def reset_column_information # :nodoc:
|
106
|
+
super
|
107
|
+
clear_caches_calculated_from_columns
|
108
|
+
end
|
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
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
existing_column_names = existing_columns.map(&:name)
|
123
|
+
new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
|
124
|
+
connection.new_column(name, nil, type)
|
125
|
+
end
|
126
|
+
|
127
|
+
existing_columns + new_columns
|
128
|
+
end
|
129
|
+
|
130
|
+
def clear_caches_calculated_from_columns
|
131
|
+
@attributes_builder = nil
|
132
|
+
@column_names = nil
|
133
|
+
@column_types = nil
|
134
|
+
@columns = nil
|
135
|
+
@columns_hash = nil
|
136
|
+
@content_columns = nil
|
137
|
+
@default_attributes = nil
|
138
|
+
@persistable_attribute_names = nil
|
139
|
+
@attribute_names = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def raw_default_values
|
143
|
+
super.merge(user_provided_defaults)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -185,7 +185,7 @@ module ActiveRecord
|
|
185
185
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
186
186
|
after_create save_method
|
187
187
|
after_update save_method
|
188
|
-
elsif reflection.
|
188
|
+
elsif reflection.has_one?
|
189
189
|
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
|
190
190
|
# Configures two callbacks instead of a single after_save so that
|
191
191
|
# the model may rely on their execution order relative to its
|
@@ -214,10 +214,7 @@ module ActiveRecord
|
|
214
214
|
method = :validate_single_association
|
215
215
|
end
|
216
216
|
|
217
|
-
define_non_cyclic_method(validation_method)
|
218
|
-
send(method, reflection)
|
219
|
-
true
|
220
|
-
end
|
217
|
+
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
221
218
|
validate validation_method
|
222
219
|
end
|
223
220
|
end
|
@@ -283,11 +280,18 @@ module ActiveRecord
|
|
283
280
|
# go through nested autosave associations that are loaded in memory (without loading
|
284
281
|
# any new ones), and return true if is changed for autosave
|
285
282
|
def nested_records_changed_for_autosave?
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
283
|
+
@_nested_records_changed_for_autosave_already_called ||= false
|
284
|
+
return false if @_nested_records_changed_for_autosave_already_called
|
285
|
+
begin
|
286
|
+
@_nested_records_changed_for_autosave_already_called = true
|
287
|
+
self.class._reflections.values.any? do |reflection|
|
288
|
+
if reflection.options[:autosave]
|
289
|
+
association = association_instance_get(reflection.name)
|
290
|
+
association && Array.wrap(association.target).any?(&:changed_for_autosave?)
|
291
|
+
end
|
290
292
|
end
|
293
|
+
ensure
|
294
|
+
@_nested_records_changed_for_autosave_already_called = false
|
291
295
|
end
|
292
296
|
end
|
293
297
|
|
@@ -314,9 +318,10 @@ module ActiveRecord
|
|
314
318
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
315
319
|
# enabled records if they're marked_for_destruction? or destroyed.
|
316
320
|
def association_valid?(reflection, record)
|
317
|
-
return true if record.destroyed? || record.marked_for_destruction?
|
321
|
+
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
|
318
322
|
|
319
|
-
|
323
|
+
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
|
324
|
+
unless valid = record.valid?(validation_context)
|
320
325
|
if reflection.options[:autosave]
|
321
326
|
record.errors.each do |attribute, message|
|
322
327
|
attribute = "#{reflection.name}.#{attribute}"
|
@@ -415,7 +420,9 @@ module ActiveRecord
|
|
415
420
|
|
416
421
|
# If the record is new or it has changed, returns true.
|
417
422
|
def record_changed?(reflection, record, key)
|
418
|
-
record.new_record? ||
|
423
|
+
record.new_record? ||
|
424
|
+
(record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
|
425
|
+
record.attribute_changed?(reflection.foreign_key)
|
419
426
|
end
|
420
427
|
|
421
428
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|