activerecord 3.1.10 → 4.2.11
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 +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- 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 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- 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 +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- 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 +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- 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 +279 -232
- data/lib/active_record/base.rb +84 -1969
- 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 +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- 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 +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- 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 +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- 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 +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- 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 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- 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 +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -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/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- 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/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- 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 -16
@@ -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
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'active_model/forbidden_attributes_protection'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module AttributeAssignment
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveModel::ForbiddenAttributesProtection
|
7
|
+
|
8
|
+
# Allows you to set all the attributes by passing in a hash of attributes with
|
9
|
+
# keys matching the attribute names (which again matches the column names).
|
10
|
+
#
|
11
|
+
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
12
|
+
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
+
# exception is raised.
|
14
|
+
#
|
15
|
+
# cat = Cat.new(name: "Gorby", status: "yawning")
|
16
|
+
# cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
|
17
|
+
# cat.assign_attributes(status: "sleeping")
|
18
|
+
# cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
|
19
|
+
#
|
20
|
+
# New attributes will be persisted in the database when the object is saved.
|
21
|
+
#
|
22
|
+
# Aliased to <tt>attributes=</tt>.
|
23
|
+
def assign_attributes(new_attributes)
|
24
|
+
if !new_attributes.respond_to?(:stringify_keys)
|
25
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
26
|
+
end
|
27
|
+
return if new_attributes.blank?
|
28
|
+
|
29
|
+
attributes = new_attributes.stringify_keys
|
30
|
+
multi_parameter_attributes = []
|
31
|
+
nested_parameter_attributes = []
|
32
|
+
|
33
|
+
attributes = sanitize_for_mass_assignment(attributes)
|
34
|
+
|
35
|
+
attributes.each do |k, v|
|
36
|
+
if k.include?("(")
|
37
|
+
multi_parameter_attributes << [ k, v ]
|
38
|
+
elsif v.is_a?(Hash)
|
39
|
+
nested_parameter_attributes << [ k, v ]
|
40
|
+
else
|
41
|
+
_assign_attribute(k, v)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
46
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
alias attributes= assign_attributes
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def _assign_attribute(k, v)
|
54
|
+
public_send("#{k}=", v)
|
55
|
+
rescue NoMethodError, NameError
|
56
|
+
if respond_to?("#{k}=")
|
57
|
+
raise
|
58
|
+
else
|
59
|
+
raise UnknownAttributeError.new(self, k)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
64
|
+
def assign_nested_parameter_attributes(pairs)
|
65
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
69
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
70
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
71
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
72
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
73
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
74
|
+
def assign_multiparameter_attributes(pairs)
|
75
|
+
execute_callstack_for_multiparameter_attributes(
|
76
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
81
|
+
errors = []
|
82
|
+
callstack.each do |name, values_with_empty_parameters|
|
83
|
+
begin
|
84
|
+
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
|
85
|
+
rescue => ex
|
86
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
unless errors.empty?
|
90
|
+
error_descriptions = errors.map { |ex| ex.message }.join(",")
|
91
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
96
|
+
attributes = {}
|
97
|
+
|
98
|
+
pairs.each do |(multiparameter_name, value)|
|
99
|
+
attribute_name = multiparameter_name.split("(").first
|
100
|
+
attributes[attribute_name] ||= {}
|
101
|
+
|
102
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
103
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
104
|
+
end
|
105
|
+
|
106
|
+
attributes
|
107
|
+
end
|
108
|
+
|
109
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
110
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_parameter_position(multiparameter_name)
|
114
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
class MultiparameterAttribute #:nodoc:
|
118
|
+
attr_reader :object, :name, :values, :cast_type
|
119
|
+
|
120
|
+
def initialize(object, name, values)
|
121
|
+
@object = object
|
122
|
+
@name = name
|
123
|
+
@values = values
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_value
|
127
|
+
return if values.values.compact.empty?
|
128
|
+
|
129
|
+
@cast_type = object.type_for_attribute(name)
|
130
|
+
klass = cast_type.klass
|
131
|
+
|
132
|
+
if klass == Time
|
133
|
+
read_time
|
134
|
+
elsif klass == Date
|
135
|
+
read_date
|
136
|
+
else
|
137
|
+
read_other
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def instantiate_time_object(set_values)
|
144
|
+
if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
|
145
|
+
Time.zone.local(*set_values)
|
146
|
+
else
|
147
|
+
Time.send(object.class.default_timezone, *set_values)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def read_time
|
152
|
+
# If column is a :time (and not :date or :datetime) there is no need to validate if
|
153
|
+
# there are year/month/day fields
|
154
|
+
if cast_type.type == :time
|
155
|
+
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
156
|
+
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
157
|
+
values[key] ||= value
|
158
|
+
end
|
159
|
+
else
|
160
|
+
# else column is a timestamp, so if Date bits were not provided, error
|
161
|
+
validate_required_parameters!([1,2,3])
|
162
|
+
|
163
|
+
# If Date bits were provided but blank, then return nil
|
164
|
+
return if blank_date_parameter?
|
165
|
+
end
|
166
|
+
|
167
|
+
max_position = extract_max_param(6)
|
168
|
+
set_values = values.values_at(*(1..max_position))
|
169
|
+
# If Time bits are not there, then default to 0
|
170
|
+
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
|
171
|
+
instantiate_time_object(set_values)
|
172
|
+
end
|
173
|
+
|
174
|
+
def read_date
|
175
|
+
return if blank_date_parameter?
|
176
|
+
set_values = values.values_at(1,2,3)
|
177
|
+
begin
|
178
|
+
Date.new(*set_values)
|
179
|
+
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
180
|
+
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def read_other
|
185
|
+
max_position = extract_max_param
|
186
|
+
positions = (1..max_position)
|
187
|
+
validate_required_parameters!(positions)
|
188
|
+
|
189
|
+
values.slice(*positions)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Checks whether some blank date parameter exists. Note that this is different
|
193
|
+
# than the validate_required_parameters! method, since it just checks for blank
|
194
|
+
# positions instead of missing ones, and does not raise in case one blank position
|
195
|
+
# exists. The caller is responsible to handle the case of this returning true.
|
196
|
+
def blank_date_parameter?
|
197
|
+
(1..3).any? { |position| values[position].blank? }
|
198
|
+
end
|
199
|
+
|
200
|
+
# If some position is not provided, it errors out a missing parameter exception.
|
201
|
+
def validate_required_parameters!(positions)
|
202
|
+
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
203
|
+
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def extract_max_param(upper_cap = 100)
|
208
|
+
[values.keys.max, upper_cap].min
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
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
|