activerecord 3.2.22.5 → 4.2.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- 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/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- 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/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,163 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Attribute # :nodoc:
|
3
|
+
class << self
|
4
|
+
def from_database(name, value, type)
|
5
|
+
FromDatabase.new(name, value, type)
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_user(name, value, type)
|
9
|
+
FromUser.new(name, value, type)
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_cast_value(name, value, type)
|
13
|
+
WithCastValue.new(name, value, type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def null(name)
|
17
|
+
Null.new(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def uninitialized(name, type)
|
21
|
+
Uninitialized.new(name, type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :name, :value_before_type_cast, :type
|
26
|
+
|
27
|
+
# This method should not be called directly.
|
28
|
+
# Use #from_database or #from_user
|
29
|
+
def initialize(name, value_before_type_cast, type)
|
30
|
+
@name = name
|
31
|
+
@value_before_type_cast = value_before_type_cast
|
32
|
+
@type = type
|
33
|
+
end
|
34
|
+
|
35
|
+
def value
|
36
|
+
# `defined?` is cheaper than `||=` when we get back falsy values
|
37
|
+
@value = original_value unless defined?(@value)
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
|
41
|
+
def original_value
|
42
|
+
type_cast(value_before_type_cast)
|
43
|
+
end
|
44
|
+
|
45
|
+
def value_for_database
|
46
|
+
type.type_cast_for_database(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def changed_from?(old_value)
|
50
|
+
type.changed?(old_value, value, value_before_type_cast)
|
51
|
+
end
|
52
|
+
|
53
|
+
def changed_in_place_from?(old_value)
|
54
|
+
has_been_read? && type.changed_in_place?(old_value, value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_value_from_user(value)
|
58
|
+
self.class.from_user(name, value, type)
|
59
|
+
end
|
60
|
+
|
61
|
+
def with_value_from_database(value)
|
62
|
+
self.class.from_database(name, value, type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_cast_value(value)
|
66
|
+
self.class.with_cast_value(name, value, type)
|
67
|
+
end
|
68
|
+
|
69
|
+
def type_cast(*)
|
70
|
+
raise NotImplementedError
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialized?
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def came_from_user?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def ==(other)
|
82
|
+
self.class == other.class &&
|
83
|
+
name == other.name &&
|
84
|
+
value_before_type_cast == other.value_before_type_cast &&
|
85
|
+
type == other.type
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def initialize_dup(other)
|
91
|
+
if defined?(@value) && @value.duplicable?
|
92
|
+
@value = @value.dup
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def has_been_read?
|
99
|
+
defined?(@value)
|
100
|
+
end
|
101
|
+
|
102
|
+
class FromDatabase < Attribute # :nodoc:
|
103
|
+
def type_cast(value)
|
104
|
+
type.type_cast_from_database(value)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class FromUser < Attribute # :nodoc:
|
109
|
+
def type_cast(value)
|
110
|
+
type.type_cast_from_user(value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def came_from_user?
|
114
|
+
true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class WithCastValue < Attribute # :nodoc:
|
119
|
+
def type_cast(value)
|
120
|
+
value
|
121
|
+
end
|
122
|
+
|
123
|
+
def changed_in_place_from?(old_value)
|
124
|
+
false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Null < Attribute # :nodoc:
|
129
|
+
def initialize(name)
|
130
|
+
super(name, nil, Type::Value.new)
|
131
|
+
end
|
132
|
+
|
133
|
+
def value
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_value_from_database(value)
|
138
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
139
|
+
end
|
140
|
+
alias_method :with_value_from_user, :with_value_from_database
|
141
|
+
end
|
142
|
+
|
143
|
+
class Uninitialized < Attribute # :nodoc:
|
144
|
+
def initialize(name, type)
|
145
|
+
super(name, nil, type)
|
146
|
+
end
|
147
|
+
|
148
|
+
def value
|
149
|
+
if block_given?
|
150
|
+
yield name
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def value_for_database
|
155
|
+
end
|
156
|
+
|
157
|
+
def initialized?
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
|
162
|
+
end
|
163
|
+
end
|
@@ -1,206 +1,103 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_model/forbidden_attributes_protection'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeAssignment
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::
|
6
|
+
include ActiveModel::ForbiddenAttributesProtection
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
|
12
|
-
def attributes_protected_by_default
|
13
|
-
default = [ primary_key, inheritance_column ]
|
14
|
-
default << 'id' unless primary_key.eql? 'id'
|
15
|
-
default
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Allows you to set all the attributes at once by passing in a hash with keys
|
20
|
-
# matching the attribute names (which again matches the column names).
|
21
|
-
#
|
22
|
-
# If any attributes are protected by either +attr_protected+ or
|
23
|
-
# +attr_accessible+ then only settable attributes will be assigned.
|
24
|
-
#
|
25
|
-
# class User < ActiveRecord::Base
|
26
|
-
# attr_protected :is_admin
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# user = User.new
|
30
|
-
# user.attributes = { :username => 'Phusion', :is_admin => true }
|
31
|
-
# user.username # => "Phusion"
|
32
|
-
# user.is_admin? # => false
|
33
|
-
def attributes=(new_attributes)
|
34
|
-
return unless new_attributes.is_a?(Hash)
|
35
|
-
|
36
|
-
assign_attributes(new_attributes)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Allows you to set all the attributes for a particular mass-assignment
|
40
|
-
# security role by passing in a hash of attributes with keys matching
|
41
|
-
# the attribute names (which again matches the column names) and the role
|
42
|
-
# name using the :as option.
|
8
|
+
# Allows you to set all the attributes by passing in a hash of attributes with
|
9
|
+
# keys matching the attribute names (which again matches the column names).
|
43
10
|
#
|
44
|
-
#
|
45
|
-
#
|
11
|
+
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
|
+
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
+
# exception is raised.
|
46
14
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
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 }
|
51
19
|
#
|
52
|
-
#
|
53
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
54
|
-
# user.name # => "Josh"
|
55
|
-
# user.is_admin? # => false
|
20
|
+
# New attributes will be persisted in the database when the object is saved.
|
56
21
|
#
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# user = User.new
|
63
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
|
64
|
-
# user.name # => "Josh"
|
65
|
-
# user.is_admin? # => true
|
66
|
-
def assign_attributes(new_attributes, options = {})
|
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
|
67
27
|
return if new_attributes.blank?
|
68
28
|
|
69
|
-
attributes
|
70
|
-
multi_parameter_attributes
|
29
|
+
attributes = new_attributes.stringify_keys
|
30
|
+
multi_parameter_attributes = []
|
71
31
|
nested_parameter_attributes = []
|
72
|
-
@mass_assignment_options = options
|
73
32
|
|
74
|
-
|
75
|
-
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
|
76
|
-
end
|
33
|
+
attributes = sanitize_for_mass_assignment(attributes)
|
77
34
|
|
78
35
|
attributes.each do |k, v|
|
79
36
|
if k.include?("(")
|
80
37
|
multi_parameter_attributes << [ k, v ]
|
81
|
-
elsif
|
82
|
-
|
83
|
-
nested_parameter_attributes << [ k, v ]
|
84
|
-
else
|
85
|
-
send("#{k}=", v)
|
86
|
-
end
|
38
|
+
elsif v.is_a?(Hash)
|
39
|
+
nested_parameter_attributes << [ k, v ]
|
87
40
|
else
|
88
|
-
|
41
|
+
_assign_attribute(k, v)
|
89
42
|
end
|
90
43
|
end
|
91
44
|
|
92
|
-
|
93
|
-
|
94
|
-
send("#{k}=", v)
|
95
|
-
end
|
96
|
-
|
97
|
-
@mass_assignment_options = nil
|
98
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
45
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
46
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
99
47
|
end
|
100
48
|
|
101
|
-
|
49
|
+
alias attributes= assign_attributes
|
102
50
|
|
103
|
-
|
104
|
-
@mass_assignment_options ||= {}
|
105
|
-
end
|
51
|
+
private
|
106
52
|
|
107
|
-
def
|
108
|
-
|
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
|
109
61
|
end
|
110
62
|
|
111
|
-
|
63
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
64
|
+
def assign_nested_parameter_attributes(pairs)
|
65
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
66
|
+
end
|
112
67
|
|
113
68
|
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
114
69
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
115
70
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
116
71
|
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
117
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for
|
118
|
-
# f for Float
|
119
|
-
# attribute will be set to nil.
|
72
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
73
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
120
74
|
def assign_multiparameter_attributes(pairs)
|
121
75
|
execute_callstack_for_multiparameter_attributes(
|
122
76
|
extract_callstack_for_multiparameter_attributes(pairs)
|
123
77
|
)
|
124
78
|
end
|
125
79
|
|
126
|
-
def instantiate_time_object(name, values)
|
127
|
-
if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
|
128
|
-
Time.zone.local(*values)
|
129
|
-
else
|
130
|
-
Time.time_with_datetime_fallback(self.class.default_timezone, *values)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
80
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
135
81
|
errors = []
|
136
82
|
callstack.each do |name, values_with_empty_parameters|
|
137
83
|
begin
|
138
|
-
send(name
|
84
|
+
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
|
139
85
|
rescue => ex
|
140
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
|
86
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
141
87
|
end
|
142
88
|
end
|
143
89
|
unless errors.empty?
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
def read_value_from_parameter(name, values_hash_from_param)
|
149
|
-
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
150
|
-
if values_hash_from_param.values.all?{|v|v.nil?}
|
151
|
-
nil
|
152
|
-
elsif klass == Time
|
153
|
-
read_time_parameter_value(name, values_hash_from_param)
|
154
|
-
elsif klass == Date
|
155
|
-
read_date_parameter_value(name, values_hash_from_param)
|
156
|
-
else
|
157
|
-
read_other_parameter_value(klass, name, values_hash_from_param)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def read_time_parameter_value(name, values_hash_from_param)
|
162
|
-
# If Date bits were not provided, error
|
163
|
-
raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
|
164
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
|
165
|
-
# If Date bits were provided but blank, then return nil
|
166
|
-
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
167
|
-
|
168
|
-
set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
|
169
|
-
# If Time bits are not there, then default to 0
|
170
|
-
(3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
|
171
|
-
instantiate_time_object(name, set_values)
|
172
|
-
end
|
173
|
-
|
174
|
-
def read_date_parameter_value(name, values_hash_from_param)
|
175
|
-
return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
|
176
|
-
set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[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(name, 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_parameter_value(klass, name, values_hash_from_param)
|
185
|
-
max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
|
186
|
-
values = (1..max_position).collect do |position|
|
187
|
-
raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
|
188
|
-
values_hash_from_param[position]
|
90
|
+
error_descriptions = errors.map { |ex| ex.message }.join(",")
|
91
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
189
92
|
end
|
190
|
-
klass.new(*values)
|
191
|
-
end
|
192
|
-
|
193
|
-
def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
|
194
|
-
[values_hash_from_param.keys.max,upper_cap].min
|
195
93
|
end
|
196
94
|
|
197
95
|
def extract_callstack_for_multiparameter_attributes(pairs)
|
198
|
-
attributes = {
|
96
|
+
attributes = {}
|
199
97
|
|
200
|
-
pairs.each do |
|
201
|
-
multiparameter_name, value = pair
|
98
|
+
pairs.each do |(multiparameter_name, value)|
|
202
99
|
attribute_name = multiparameter_name.split("(").first
|
203
|
-
attributes[attribute_name]
|
100
|
+
attributes[attribute_name] ||= {}
|
204
101
|
|
205
102
|
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
206
103
|
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
@@ -217,5 +114,99 @@ module ActiveRecord
|
|
217
114
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
218
115
|
end
|
219
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
|
220
211
|
end
|
221
212
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeDecorators # :nodoc:
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
|
7
|
+
self.attribute_type_decorations = TypeDecorator.new
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods # :nodoc:
|
11
|
+
def decorate_attribute_type(column_name, decorator_name, &block)
|
12
|
+
matcher = ->(name, _) { name == column_name.to_s }
|
13
|
+
key = "_#{column_name}_#{decorator_name}"
|
14
|
+
decorate_matching_attribute_types(matcher, key, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
18
|
+
clear_caches_calculated_from_columns
|
19
|
+
decorator_name = decorator_name.to_s
|
20
|
+
|
21
|
+
# Create new hashes so we don't modify parent classes
|
22
|
+
self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def add_user_provided_columns(*)
|
28
|
+
super.map do |column|
|
29
|
+
decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
|
30
|
+
column.with_type(decorated_type)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TypeDecorator # :nodoc:
|
36
|
+
delegate :clear, to: :@decorations
|
37
|
+
|
38
|
+
def initialize(decorations = {})
|
39
|
+
@decorations = decorations
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge(*args)
|
43
|
+
TypeDecorator.new(@decorations.merge(*args))
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply(name, type)
|
47
|
+
decorations = decorators_for(name, type)
|
48
|
+
decorations.inject(type) do |new_type, block|
|
49
|
+
block.call(new_type)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def decorators_for(name, type)
|
56
|
+
matching(name, type).map(&:last)
|
57
|
+
end
|
58
|
+
|
59
|
+
def matching(name, type)
|
60
|
+
@decorations.values.select do |(matcher, _)|
|
61
|
+
matcher.call(name, type)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,30 +1,75 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module AttributeMethods
|
3
|
+
# = Active Record Attribute Methods Before Type Cast
|
4
|
+
#
|
5
|
+
# <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
|
6
|
+
# read the value of the attributes before typecasting and deserialization.
|
7
|
+
#
|
8
|
+
# class Task < ActiveRecord::Base
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
12
|
+
# task.id # => 1
|
13
|
+
# task.completed_on # => Sun, 21 Oct 2012
|
14
|
+
#
|
15
|
+
# task.attributes_before_type_cast
|
16
|
+
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
|
17
|
+
# task.read_attribute_before_type_cast('id') # => "1"
|
18
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
19
|
+
#
|
20
|
+
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
|
21
|
+
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
|
22
|
+
# suffix.
|
23
|
+
#
|
24
|
+
# task.id_before_type_cast # => "1"
|
25
|
+
# task.completed_on_before_type_cast # => "2012-10-21"
|
3
26
|
module BeforeTypeCast
|
4
27
|
extend ActiveSupport::Concern
|
5
28
|
|
6
29
|
included do
|
7
30
|
attribute_method_suffix "_before_type_cast"
|
31
|
+
attribute_method_suffix "_came_from_user?"
|
8
32
|
end
|
9
33
|
|
34
|
+
# Returns the value of the attribute identified by +attr_name+ before
|
35
|
+
# typecasting and deserialization.
|
36
|
+
#
|
37
|
+
# class Task < ActiveRecord::Base
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# task = Task.new(id: '1', completed_on: '2012-10-21')
|
41
|
+
# task.read_attribute('id') # => 1
|
42
|
+
# task.read_attribute_before_type_cast('id') # => '1'
|
43
|
+
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
|
44
|
+
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
45
|
+
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
10
46
|
def read_attribute_before_type_cast(attr_name)
|
11
|
-
@attributes[attr_name]
|
47
|
+
@attributes[attr_name.to_s].value_before_type_cast
|
12
48
|
end
|
13
49
|
|
14
50
|
# Returns a hash of attributes before typecasting and deserialization.
|
51
|
+
#
|
52
|
+
# class Task < ActiveRecord::Base
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
|
56
|
+
# task.attributes
|
57
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
|
58
|
+
# task.attributes_before_type_cast
|
59
|
+
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
15
60
|
def attributes_before_type_cast
|
16
|
-
@attributes
|
61
|
+
@attributes.values_before_type_cast
|
17
62
|
end
|
18
63
|
|
19
64
|
private
|
20
65
|
|
21
66
|
# Handle *_before_type_cast for method_missing.
|
22
67
|
def attribute_before_type_cast(attribute_name)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
68
|
+
read_attribute_before_type_cast(attribute_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def attribute_came_from_user?(attribute_name)
|
72
|
+
@attributes[attribute_name].came_from_user?
|
28
73
|
end
|
29
74
|
end
|
30
75
|
end
|