activerecord 3.2.19 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- 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 +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -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 +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -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/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -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 +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- 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/mysql_adapter.rb +0 -441
- 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/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- 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,28 @@
|
|
1
|
+
require 'active_record/attribute'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Attribute # :nodoc:
|
5
|
+
class UserProvidedDefault < FromUser # :nodoc:
|
6
|
+
def initialize(name, value, type, database_default)
|
7
|
+
@user_provided_value = value
|
8
|
+
super(name, value, type, database_default)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value_before_type_cast
|
12
|
+
if user_provided_value.is_a?(Proc)
|
13
|
+
@memoized_value_before_type_cast ||= user_provided_value.call
|
14
|
+
else
|
15
|
+
@user_provided_value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_type(type)
|
20
|
+
self.class.new(name, user_provided_value, type, original_attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :user_provided_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,213 @@
|
|
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, original_attribute = nil)
|
9
|
+
FromUser.new(name, value, type, original_attribute)
|
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, original_attribute = nil)
|
30
|
+
@name = name
|
31
|
+
@value_before_type_cast = value_before_type_cast
|
32
|
+
@type = type
|
33
|
+
@original_attribute = original_attribute
|
34
|
+
end
|
35
|
+
|
36
|
+
def value
|
37
|
+
# `defined?` is cheaper than `||=` when we get back falsy values
|
38
|
+
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
39
|
+
@value
|
40
|
+
end
|
41
|
+
|
42
|
+
def original_value
|
43
|
+
if assigned?
|
44
|
+
original_attribute.original_value
|
45
|
+
else
|
46
|
+
type_cast(value_before_type_cast)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def value_for_database
|
51
|
+
type.serialize(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed?
|
55
|
+
changed_from_assignment? || changed_in_place?
|
56
|
+
end
|
57
|
+
|
58
|
+
def changed_in_place?
|
59
|
+
has_been_read? && type.changed_in_place?(original_value_for_database, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def forgetting_assignment
|
63
|
+
with_value_from_database(value_for_database)
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_value_from_user(value)
|
67
|
+
type.assert_valid_value(value)
|
68
|
+
self.class.from_user(name, value, type, self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def with_value_from_database(value)
|
72
|
+
self.class.from_database(name, value, type)
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_cast_value(value)
|
76
|
+
self.class.with_cast_value(name, value, type)
|
77
|
+
end
|
78
|
+
|
79
|
+
def with_type(type)
|
80
|
+
self.class.new(name, value_before_type_cast, type, original_attribute)
|
81
|
+
end
|
82
|
+
|
83
|
+
def type_cast(*)
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialized?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def came_from_user?
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_been_read?
|
96
|
+
defined?(@value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def ==(other)
|
100
|
+
self.class == other.class &&
|
101
|
+
name == other.name &&
|
102
|
+
value_before_type_cast == other.value_before_type_cast &&
|
103
|
+
type == other.type
|
104
|
+
end
|
105
|
+
alias eql? ==
|
106
|
+
|
107
|
+
def hash
|
108
|
+
[self.class, name, value_before_type_cast, type].hash
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
attr_reader :original_attribute
|
114
|
+
alias_method :assigned?, :original_attribute
|
115
|
+
|
116
|
+
def initialize_dup(other)
|
117
|
+
if defined?(@value) && @value.duplicable?
|
118
|
+
@value = @value.dup
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def changed_from_assignment?
|
123
|
+
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
124
|
+
end
|
125
|
+
|
126
|
+
def original_value_for_database
|
127
|
+
if assigned?
|
128
|
+
original_attribute.original_value_for_database
|
129
|
+
else
|
130
|
+
_original_value_for_database
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def _original_value_for_database
|
135
|
+
value_for_database
|
136
|
+
end
|
137
|
+
|
138
|
+
class FromDatabase < Attribute # :nodoc:
|
139
|
+
def type_cast(value)
|
140
|
+
type.deserialize(value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def _original_value_for_database
|
144
|
+
value_before_type_cast
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class FromUser < Attribute # :nodoc:
|
149
|
+
def type_cast(value)
|
150
|
+
type.cast(value)
|
151
|
+
end
|
152
|
+
|
153
|
+
def came_from_user?
|
154
|
+
true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class WithCastValue < Attribute # :nodoc:
|
159
|
+
def type_cast(value)
|
160
|
+
value
|
161
|
+
end
|
162
|
+
|
163
|
+
def changed_in_place_from?(old_value)
|
164
|
+
false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class Null < Attribute # :nodoc:
|
169
|
+
def initialize(name)
|
170
|
+
super(name, nil, Type::Value.new)
|
171
|
+
end
|
172
|
+
|
173
|
+
def type_cast(*)
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
|
177
|
+
def with_type(type)
|
178
|
+
self.class.with_cast_value(name, nil, type)
|
179
|
+
end
|
180
|
+
|
181
|
+
def with_value_from_database(value)
|
182
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
183
|
+
end
|
184
|
+
alias_method :with_value_from_user, :with_value_from_database
|
185
|
+
end
|
186
|
+
|
187
|
+
class Uninitialized < Attribute # :nodoc:
|
188
|
+
UNINITIALIZED_ORIGINAL_VALUE = Object.new
|
189
|
+
|
190
|
+
def initialize(name, type)
|
191
|
+
super(name, nil, type)
|
192
|
+
end
|
193
|
+
|
194
|
+
def value
|
195
|
+
if block_given?
|
196
|
+
yield name
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def original_value
|
201
|
+
UNINITIALIZED_ORIGINAL_VALUE
|
202
|
+
end
|
203
|
+
|
204
|
+
def value_for_database
|
205
|
+
end
|
206
|
+
|
207
|
+
def initialized?
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
212
|
+
end
|
213
|
+
end
|
@@ -1,206 +1,77 @@
|
|
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::AttributeAssignment
|
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)
|
8
|
+
# Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
|
9
|
+
def attributes=(attributes)
|
10
|
+
assign_attributes(attributes)
|
37
11
|
end
|
38
12
|
|
39
|
-
|
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.
|
43
|
-
#
|
44
|
-
# To bypass mass-assignment security you can use the :without_protection => true
|
45
|
-
# option.
|
46
|
-
#
|
47
|
-
# class User < ActiveRecord::Base
|
48
|
-
# attr_accessible :name
|
49
|
-
# attr_accessible :name, :is_admin, :as => :admin
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# user = User.new
|
53
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true })
|
54
|
-
# user.name # => "Josh"
|
55
|
-
# user.is_admin? # => false
|
56
|
-
#
|
57
|
-
# user = User.new
|
58
|
-
# user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
|
59
|
-
# user.name # => "Josh"
|
60
|
-
# user.is_admin? # => true
|
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 = {})
|
67
|
-
return if new_attributes.blank?
|
68
|
-
|
69
|
-
attributes = new_attributes.stringify_keys
|
70
|
-
multi_parameter_attributes = []
|
71
|
-
nested_parameter_attributes = []
|
72
|
-
@mass_assignment_options = options
|
13
|
+
private
|
73
14
|
|
74
|
-
|
75
|
-
|
76
|
-
|
15
|
+
def _assign_attributes(attributes) # :nodoc:
|
16
|
+
multi_parameter_attributes = {}
|
17
|
+
nested_parameter_attributes = {}
|
77
18
|
|
78
19
|
attributes.each do |k, v|
|
79
20
|
if k.include?("(")
|
80
|
-
multi_parameter_attributes
|
81
|
-
elsif
|
82
|
-
|
83
|
-
nested_parameter_attributes << [ k, v ]
|
84
|
-
else
|
85
|
-
send("#{k}=", v)
|
86
|
-
end
|
87
|
-
else
|
88
|
-
raise(UnknownAttributeError, "unknown attribute: #{k}")
|
21
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
22
|
+
elsif v.is_a?(Hash)
|
23
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
89
24
|
end
|
90
25
|
end
|
26
|
+
super(attributes)
|
91
27
|
|
92
|
-
|
93
|
-
|
94
|
-
send("#{k}=", v)
|
95
|
-
end
|
96
|
-
|
97
|
-
@mass_assignment_options = nil
|
98
|
-
assign_multiparameter_attributes(multi_parameter_attributes)
|
99
|
-
end
|
100
|
-
|
101
|
-
protected
|
102
|
-
|
103
|
-
def mass_assignment_options
|
104
|
-
@mass_assignment_options ||= {}
|
28
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
29
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
105
30
|
end
|
106
31
|
|
107
|
-
|
108
|
-
|
32
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
33
|
+
def assign_nested_parameter_attributes(pairs)
|
34
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
109
35
|
end
|
110
36
|
|
111
|
-
private
|
112
|
-
|
113
37
|
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
114
38
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
115
39
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
116
40
|
# 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.
|
41
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
42
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
120
43
|
def assign_multiparameter_attributes(pairs)
|
121
44
|
execute_callstack_for_multiparameter_attributes(
|
122
45
|
extract_callstack_for_multiparameter_attributes(pairs)
|
123
46
|
)
|
124
47
|
end
|
125
48
|
|
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
49
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
135
50
|
errors = []
|
136
51
|
callstack.each do |name, values_with_empty_parameters|
|
137
52
|
begin
|
138
|
-
|
53
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
54
|
+
values = nil
|
55
|
+
else
|
56
|
+
values = values_with_empty_parameters
|
57
|
+
end
|
58
|
+
send("#{name}=", values)
|
139
59
|
rescue => ex
|
140
|
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
|
60
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
141
61
|
end
|
142
62
|
end
|
143
63
|
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
|
64
|
+
error_descriptions = errors.map(&:message).join(",")
|
65
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
181
66
|
end
|
182
67
|
end
|
183
68
|
|
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]
|
189
|
-
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
|
-
end
|
196
|
-
|
197
69
|
def extract_callstack_for_multiparameter_attributes(pairs)
|
198
|
-
attributes = {
|
70
|
+
attributes = {}
|
199
71
|
|
200
|
-
pairs.each do |
|
201
|
-
multiparameter_name, value = pair
|
72
|
+
pairs.each do |(multiparameter_name, value)|
|
202
73
|
attribute_name = multiparameter_name.split("(").first
|
203
|
-
attributes[attribute_name]
|
74
|
+
attributes[attribute_name] ||= {}
|
204
75
|
|
205
76
|
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
206
77
|
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
@@ -216,6 +87,5 @@ module ActiveRecord
|
|
216
87
|
def find_parameter_position(multiparameter_name)
|
217
88
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
218
89
|
end
|
219
|
-
|
220
90
|
end
|
221
91
|
end
|
@@ -0,0 +1,67 @@
|
|
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
|
+
reload_schema_from_cache
|
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 load_schema!
|
28
|
+
super
|
29
|
+
attribute_types.each do |name, type|
|
30
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
31
|
+
define_attribute(name, decorated_type)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TypeDecorator # :nodoc:
|
37
|
+
delegate :clear, to: :@decorations
|
38
|
+
|
39
|
+
def initialize(decorations = {})
|
40
|
+
@decorations = decorations
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge(*args)
|
44
|
+
TypeDecorator.new(@decorations.merge(*args))
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply(name, type)
|
48
|
+
decorations = decorators_for(name, type)
|
49
|
+
decorations.inject(type) do |new_type, block|
|
50
|
+
block.call(new_type)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def decorators_for(name, type)
|
57
|
+
matching(name, type).map(&:last)
|
58
|
+
end
|
59
|
+
|
60
|
+
def matching(name, type)
|
61
|
+
@decorations.values.select do |(matcher, _)|
|
62
|
+
matcher.call(name, type)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,30 +1,75 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module AttributeMethods
|
3
|
+
# = Active Record Attribute Methods Before Type Cast
|
4
|
+
#
|
5
|
+
# ActiveRecord::AttributeMethods::BeforeTypeCast 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
|