activerecord 1.0.0 → 4.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 +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +213 -0
- data/examples/performance.rb +172 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +180 -84
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +92 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
- data/lib/active_record/associations/has_many_association.rb +116 -85
- data/lib/active_record/associations/has_many_through_association.rb +197 -0
- data/lib/active_record/associations/has_one_association.rb +102 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +1437 -431
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
- data/lib/active_record/attribute_methods/dirty.rb +118 -0
- data/lib/active_record/attribute_methods/primary_key.rb +122 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +107 -0
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
- data/lib/active_record/attribute_methods/write.rb +63 -0
- data/lib/active_record/attribute_methods.rb +393 -0
- data/lib/active_record/autosave_association.rb +426 -0
- data/lib/active_record/base.rb +268 -930
- data/lib/active_record/callbacks.rb +203 -230
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +122 -0
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +213 -0
- 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 +55 -0
- data/lib/active_record/fixtures.rb +892 -138
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +181 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +82 -0
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +1015 -0
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +546 -0
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +509 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +205 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +402 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +544 -87
- data/lib/active_record/relation/batches.rb +93 -0
- data/lib/active_record/relation/calculations.rb +399 -0
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +349 -0
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +106 -0
- data/lib/active_record/relation/query_methods.rb +1044 -0
- data/lib/active_record/relation/spawn_methods.rb +73 -0
- data/lib/active_record/relation.rb +655 -0
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +65 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +197 -0
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +96 -0
- data/lib/active_record/timestamp.rb +119 -0
- data/lib/active_record/transactions.rb +366 -69
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +49 -0
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +225 -0
- data/lib/active_record/validations.rb +64 -185
- data/lib/active_record/version.rb +11 -0
- data/lib/active_record.rb +149 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record.rb +23 -0
- metadata +261 -161
- data/CHANGELOG +0 -581
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.png +0 -0
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/associations/association_collection.rb +0 -70
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/observer.rb +0 -71
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -0,0 +1,162 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Serialization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# Returns a hash of all the attributes that have been specified for
|
8
|
+
# serialization as keys and their class restriction as values.
|
9
|
+
class_attribute :serialized_attributes, instance_accessor: false
|
10
|
+
self.serialized_attributes = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
##
|
15
|
+
# :method: serialized_attributes
|
16
|
+
#
|
17
|
+
# Returns a hash of all the attributes that have been specified for
|
18
|
+
# serialization as keys and their class restriction as values.
|
19
|
+
|
20
|
+
# If you have an attribute that needs to be saved to the database as an
|
21
|
+
# object, and retrieved as the same object, then specify the name of that
|
22
|
+
# attribute using this method and it will be handled automatically. The
|
23
|
+
# serialization is done through YAML. If +class_name+ is specified, the
|
24
|
+
# serialized object must be of that class on retrieval or
|
25
|
+
# <tt>SerializationTypeMismatch</tt> will be raised.
|
26
|
+
#
|
27
|
+
# ==== Parameters
|
28
|
+
#
|
29
|
+
# * +attr_name+ - The field name that should be serialized.
|
30
|
+
# * +class_name+ - Optional, class name that the object type should be equal to.
|
31
|
+
#
|
32
|
+
# ==== Example
|
33
|
+
#
|
34
|
+
# # Serialize a preferences attribute.
|
35
|
+
# class User < ActiveRecord::Base
|
36
|
+
# serialize :preferences
|
37
|
+
# end
|
38
|
+
def serialize(attr_name, class_name = Object)
|
39
|
+
include Behavior
|
40
|
+
|
41
|
+
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
42
|
+
class_name
|
43
|
+
else
|
44
|
+
Coders::YAMLColumn.new(class_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
|
48
|
+
# has its own hash of own serialized attributes
|
49
|
+
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
|
54
|
+
def serialized_attributes
|
55
|
+
message = "Instance level serialized_attributes method is deprecated, please use class level method."
|
56
|
+
ActiveSupport::Deprecation.warn message
|
57
|
+
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
|
58
|
+
end
|
59
|
+
|
60
|
+
class Type # :nodoc:
|
61
|
+
def initialize(column)
|
62
|
+
@column = column
|
63
|
+
end
|
64
|
+
|
65
|
+
def type_cast(value)
|
66
|
+
if value.state == :serialized
|
67
|
+
value.unserialized_value @column.type_cast value.value
|
68
|
+
else
|
69
|
+
value.unserialized_value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def type
|
74
|
+
@column.type
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
|
79
|
+
def unserialized_value(v = value)
|
80
|
+
state == :serialized ? unserialize(v) : value
|
81
|
+
end
|
82
|
+
|
83
|
+
def serialized_value
|
84
|
+
state == :unserialized ? serialize : value
|
85
|
+
end
|
86
|
+
|
87
|
+
def unserialize(v)
|
88
|
+
self.state = :unserialized
|
89
|
+
self.value = coder.load(v)
|
90
|
+
end
|
91
|
+
|
92
|
+
def serialize
|
93
|
+
self.state = :serialized
|
94
|
+
self.value = coder.dump(value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# This is only added to the model when serialize is called, which
|
99
|
+
# ensures we do not make things slower when serialization is not used.
|
100
|
+
module Behavior # :nodoc:
|
101
|
+
extend ActiveSupport::Concern
|
102
|
+
|
103
|
+
module ClassMethods # :nodoc:
|
104
|
+
def initialize_attributes(attributes, options = {})
|
105
|
+
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
|
106
|
+
super(attributes, options)
|
107
|
+
|
108
|
+
serialized_attributes.each do |key, coder|
|
109
|
+
if attributes.key?(key)
|
110
|
+
attributes[key] = Attribute.new(coder, attributes[key], serialized)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
attributes
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def type_cast_attribute_for_write(column, value)
|
119
|
+
if column && coder = self.class.serialized_attributes[column.name]
|
120
|
+
Attribute.new(coder, value, :unserialized)
|
121
|
+
else
|
122
|
+
super
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def _field_changed?(attr, old, value)
|
127
|
+
if self.class.serialized_attributes.include?(attr)
|
128
|
+
old != value
|
129
|
+
else
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def read_attribute_before_type_cast(attr_name)
|
135
|
+
if self.class.serialized_attributes.include?(attr_name)
|
136
|
+
super.unserialized_value
|
137
|
+
else
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def attributes_before_type_cast
|
143
|
+
super.dup.tap do |attributes|
|
144
|
+
self.class.serialized_attributes.each_key do |key|
|
145
|
+
if attributes.key?(key)
|
146
|
+
attributes[key] = attributes[key].unserialized_value
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def typecasted_attribute_value(name)
|
153
|
+
if self.class.serialized_attributes.include?(name)
|
154
|
+
@attributes[name].serialized_value
|
155
|
+
else
|
156
|
+
super
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module TimeZoneConversion
|
4
|
+
class Type # :nodoc:
|
5
|
+
def initialize(column)
|
6
|
+
@column = column
|
7
|
+
end
|
8
|
+
|
9
|
+
def type_cast(value)
|
10
|
+
value = @column.type_cast(value)
|
11
|
+
value.acts_like?(:time) ? value.in_time_zone : value
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
@column.type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
mattr_accessor :time_zone_aware_attributes, instance_writer: false
|
23
|
+
self.time_zone_aware_attributes = false
|
24
|
+
|
25
|
+
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
|
26
|
+
self.skip_time_zone_conversion_for_attributes = []
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
protected
|
31
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
32
|
+
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
33
|
+
def define_method_attribute=(attr_name)
|
34
|
+
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
35
|
+
method_body, line = <<-EOV, __LINE__ + 1
|
36
|
+
def #{attr_name}=(time)
|
37
|
+
time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
|
38
|
+
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
39
|
+
write_attribute(:#{attr_name}, time)
|
40
|
+
#{attr_name}_will_change! if previous_time != time_with_zone
|
41
|
+
@attributes_cache["#{attr_name}"] = time_with_zone
|
42
|
+
end
|
43
|
+
EOV
|
44
|
+
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def create_time_zone_conversion_attribute?(name, column)
|
52
|
+
time_zone_aware_attributes &&
|
53
|
+
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
|
54
|
+
[:datetime, :timestamp].include?(column.type)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Write
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
attribute_method_suffix "="
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
protected
|
12
|
+
|
13
|
+
# See define_method_attribute in read.rb for an explanation of
|
14
|
+
# this code.
|
15
|
+
def define_method_attribute=(name)
|
16
|
+
safe_name = name.unpack('h*').first
|
17
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
18
|
+
def __temp__#{safe_name}=(value)
|
19
|
+
write_attribute(AttrNames::ATTR_#{safe_name}, value)
|
20
|
+
end
|
21
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
22
|
+
undef_method :__temp__#{safe_name}=
|
23
|
+
STR
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the
|
28
|
+
# specified +value+. Empty strings for fixnum and float columns are
|
29
|
+
# turned into +nil+.
|
30
|
+
def write_attribute(attr_name, value)
|
31
|
+
attr_name = attr_name.to_s
|
32
|
+
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
33
|
+
@attributes_cache.delete(attr_name)
|
34
|
+
column = column_for_attribute(attr_name)
|
35
|
+
|
36
|
+
# If we're dealing with a binary column, write the data to the cache
|
37
|
+
# so we don't attempt to typecast multiple times.
|
38
|
+
if column && column.binary?
|
39
|
+
@attributes_cache[attr_name] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
if column || @attributes.has_key?(attr_name)
|
43
|
+
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
|
44
|
+
else
|
45
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias_method :raw_write_attribute, :write_attribute
|
49
|
+
|
50
|
+
private
|
51
|
+
# Handle *= for method_missing.
|
52
|
+
def attribute=(attribute_name, value)
|
53
|
+
write_attribute(attribute_name, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def type_cast_attribute_for_write(column, value)
|
57
|
+
return value unless column
|
58
|
+
|
59
|
+
column.type_cast_for_write value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'active_support/core_ext/enumerable'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# = Active Record Attribute Methods
|
5
|
+
module AttributeMethods
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveModel::AttributeMethods
|
8
|
+
|
9
|
+
included do
|
10
|
+
include Read
|
11
|
+
include Write
|
12
|
+
include BeforeTypeCast
|
13
|
+
include Query
|
14
|
+
include PrimaryKey
|
15
|
+
include TimeZoneConversion
|
16
|
+
include Dirty
|
17
|
+
include Serialization
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Generates all the attribute related methods for columns in the database
|
22
|
+
# accessors, mutators and query methods.
|
23
|
+
def define_attribute_methods # :nodoc:
|
24
|
+
# Use a mutex; we don't want two thread simultaneously trying to define
|
25
|
+
# attribute methods.
|
26
|
+
@attribute_methods_mutex.synchronize do
|
27
|
+
return if attribute_methods_generated?
|
28
|
+
superclass.define_attribute_methods unless self == base_class
|
29
|
+
super(column_names)
|
30
|
+
@attribute_methods_generated = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def attribute_methods_generated? # :nodoc:
|
35
|
+
@attribute_methods_generated ||= false
|
36
|
+
end
|
37
|
+
|
38
|
+
def undefine_attribute_methods # :nodoc:
|
39
|
+
super if attribute_methods_generated?
|
40
|
+
@attribute_methods_generated = false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
|
44
|
+
# \Active \Record method is defined in the model, otherwise +false+.
|
45
|
+
#
|
46
|
+
# class Person < ActiveRecord::Base
|
47
|
+
# def save
|
48
|
+
# 'already defined by Active Record'
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# Person.instance_method_already_implemented?(:save)
|
53
|
+
# # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
|
54
|
+
#
|
55
|
+
# Person.instance_method_already_implemented?(:name)
|
56
|
+
# # => false
|
57
|
+
def instance_method_already_implemented?(method_name)
|
58
|
+
if dangerous_attribute_method?(method_name)
|
59
|
+
raise DangerousAttributeError, "#{method_name} is defined by Active Record"
|
60
|
+
end
|
61
|
+
|
62
|
+
if superclass == Base
|
63
|
+
super
|
64
|
+
else
|
65
|
+
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
|
66
|
+
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
|
67
|
+
defined && !ActiveRecord::Base.method_defined?(method_name) || super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# A method name is 'dangerous' if it is already defined by Active Record, but
|
72
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
73
|
+
def dangerous_attribute_method?(name) # :nodoc:
|
74
|
+
method_defined_within?(name, Base)
|
75
|
+
end
|
76
|
+
|
77
|
+
def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
|
78
|
+
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
79
|
+
if sup.method_defined?(name) || sup.private_method_defined?(name)
|
80
|
+
klass.instance_method(name).owner != sup.instance_method(name).owner
|
81
|
+
else
|
82
|
+
true
|
83
|
+
end
|
84
|
+
else
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns +true+ if +attribute+ is an attribute method and table exists,
|
90
|
+
# +false+ otherwise.
|
91
|
+
#
|
92
|
+
# class Person < ActiveRecord::Base
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# Person.attribute_method?('name') # => true
|
96
|
+
# Person.attribute_method?(:age=) # => true
|
97
|
+
# Person.attribute_method?(:nothing) # => false
|
98
|
+
def attribute_method?(attribute)
|
99
|
+
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns an array of column names as strings if it's not an abstract class and
|
103
|
+
# table exists. Otherwise it returns an empty array.
|
104
|
+
#
|
105
|
+
# class Person < ActiveRecord::Base
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# Person.attribute_names
|
109
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
110
|
+
def attribute_names
|
111
|
+
@attribute_names ||= if !abstract_class? && table_exists?
|
112
|
+
column_names
|
113
|
+
else
|
114
|
+
[]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# If we haven't generated any methods yet, generate them, then
|
120
|
+
# see if we've created the method we're looking for.
|
121
|
+
def method_missing(method, *args, &block) # :nodoc:
|
122
|
+
unless self.class.attribute_methods_generated?
|
123
|
+
self.class.define_attribute_methods
|
124
|
+
|
125
|
+
if respond_to_without_attributes?(method)
|
126
|
+
send(method, *args, &block)
|
127
|
+
else
|
128
|
+
super
|
129
|
+
end
|
130
|
+
else
|
131
|
+
super
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def attribute_missing(match, *args, &block) # :nodoc:
|
136
|
+
if self.class.columns_hash[match.attr_name]
|
137
|
+
ActiveSupport::Deprecation.warn(
|
138
|
+
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
|
139
|
+
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
|
140
|
+
"is a column of the table. If this error has happened through normal usage of Active " \
|
141
|
+
"Record (rather than through your own code or external libraries), please report it as " \
|
142
|
+
"a bug."
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
149
|
+
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
150
|
+
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
|
151
|
+
# which will all return +true+. It also define the attribute methods if they have
|
152
|
+
# not been generated.
|
153
|
+
#
|
154
|
+
# class Person < ActiveRecord::Base
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# person = Person.new
|
158
|
+
# person.respond_to(:name) # => true
|
159
|
+
# person.respond_to(:name=) # => true
|
160
|
+
# person.respond_to(:name?) # => true
|
161
|
+
# person.respond_to('age') # => true
|
162
|
+
# person.respond_to('age=') # => true
|
163
|
+
# person.respond_to('age?') # => true
|
164
|
+
# person.respond_to(:nothing) # => false
|
165
|
+
def respond_to?(name, include_private = false)
|
166
|
+
name = name.to_s
|
167
|
+
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
|
168
|
+
result = super
|
169
|
+
|
170
|
+
# If the result is false the answer is false.
|
171
|
+
return false unless result
|
172
|
+
|
173
|
+
# If the result is true then check for the select case.
|
174
|
+
# For queries selecting a subset of columns, return false for unselected columns.
|
175
|
+
# We check defined?(@attributes) not to issue warnings if called on objects that
|
176
|
+
# have been allocated but not yet initialized.
|
177
|
+
if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
|
178
|
+
return has_attribute?(name)
|
179
|
+
end
|
180
|
+
|
181
|
+
return true
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
|
185
|
+
#
|
186
|
+
# class Person < ActiveRecord::Base
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# person = Person.new
|
190
|
+
# person.has_attribute?(:name) # => true
|
191
|
+
# person.has_attribute?('age') # => true
|
192
|
+
# person.has_attribute?(:nothing) # => false
|
193
|
+
def has_attribute?(attr_name)
|
194
|
+
@attributes.has_key?(attr_name.to_s)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns an array of names for the attributes available on this object.
|
198
|
+
#
|
199
|
+
# class Person < ActiveRecord::Base
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# person = Person.new
|
203
|
+
# person.attribute_names
|
204
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
205
|
+
def attribute_names
|
206
|
+
@attributes.keys
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
210
|
+
#
|
211
|
+
# class Person < ActiveRecord::Base
|
212
|
+
# end
|
213
|
+
#
|
214
|
+
# person = Person.create(name: 'Francesco', age: 22)
|
215
|
+
# person.attributes
|
216
|
+
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
|
217
|
+
def attributes
|
218
|
+
attribute_names.each_with_object({}) { |name, attrs|
|
219
|
+
attrs[name] = read_attribute(name)
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
224
|
+
# attribute +attr_name+. String attributes are truncated upto 50
|
225
|
+
# characters, and Date and Time attributes are returned in the
|
226
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
227
|
+
# <tt>#inspect</tt> without modification.
|
228
|
+
#
|
229
|
+
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
|
230
|
+
#
|
231
|
+
# person.attribute_for_inspect(:name)
|
232
|
+
# # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
|
233
|
+
#
|
234
|
+
# person.attribute_for_inspect(:created_at)
|
235
|
+
# # => "\"2012-10-22 00:15:07\""
|
236
|
+
def attribute_for_inspect(attr_name)
|
237
|
+
value = read_attribute(attr_name)
|
238
|
+
|
239
|
+
if value.is_a?(String) && value.length > 50
|
240
|
+
"#{value[0..50]}...".inspect
|
241
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
242
|
+
%("#{value.to_s(:db)}")
|
243
|
+
else
|
244
|
+
value.inspect
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns +true+ if the specified +attribute+ has been set by the user or by a
|
249
|
+
# database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
|
250
|
+
# to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
|
251
|
+
# Note that it always returns +true+ with boolean attributes.
|
252
|
+
#
|
253
|
+
# class Task < ActiveRecord::Base
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# person = Task.new(title: '', is_done: false)
|
257
|
+
# person.attribute_present?(:title) # => false
|
258
|
+
# person.attribute_present?(:is_done) # => true
|
259
|
+
# person.name = 'Francesco'
|
260
|
+
# person.is_done = true
|
261
|
+
# person.attribute_present?(:title) # => true
|
262
|
+
# person.attribute_present?(:is_done) # => true
|
263
|
+
def attribute_present?(attribute)
|
264
|
+
value = read_attribute(attribute)
|
265
|
+
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns the column object for the named attribute. Returns +nil+ if the
|
269
|
+
# named attribute not exists.
|
270
|
+
#
|
271
|
+
# class Person < ActiveRecord::Base
|
272
|
+
# end
|
273
|
+
#
|
274
|
+
# person = Person.new
|
275
|
+
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
|
276
|
+
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
|
277
|
+
#
|
278
|
+
# person.column_for_attribute(:nothing)
|
279
|
+
# # => nil
|
280
|
+
def column_for_attribute(name)
|
281
|
+
# FIXME: should this return a null object for columns that don't exist?
|
282
|
+
self.class.columns_hash[name.to_s]
|
283
|
+
end
|
284
|
+
|
285
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
286
|
+
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
|
287
|
+
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
|
288
|
+
#
|
289
|
+
# Alias for the <tt>read_attribute</tt> method.
|
290
|
+
#
|
291
|
+
# class Person < ActiveRecord::Base
|
292
|
+
# belongs_to :organization
|
293
|
+
# end
|
294
|
+
#
|
295
|
+
# person = Person.new(name: 'Francesco', age: '22')
|
296
|
+
# person[:name] # => "Francesco"
|
297
|
+
# person[:age] # => 22
|
298
|
+
#
|
299
|
+
# person = Person.select('id').first
|
300
|
+
# person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
|
301
|
+
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
|
302
|
+
def [](attr_name)
|
303
|
+
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
304
|
+
end
|
305
|
+
|
306
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
307
|
+
# (Alias for the protected <tt>write_attribute</tt> method).
|
308
|
+
#
|
309
|
+
# class Person < ActiveRecord::Base
|
310
|
+
# end
|
311
|
+
#
|
312
|
+
# person = Person.new
|
313
|
+
# person[:age] = '22'
|
314
|
+
# person[:age] # => 22
|
315
|
+
# person[:age] # => Fixnum
|
316
|
+
def []=(attr_name, value)
|
317
|
+
write_attribute(attr_name, value)
|
318
|
+
end
|
319
|
+
|
320
|
+
protected
|
321
|
+
|
322
|
+
def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
|
323
|
+
attribute_names.each do |name|
|
324
|
+
attributes[name] = clone_attribute_value(reader_method, name)
|
325
|
+
end
|
326
|
+
attributes
|
327
|
+
end
|
328
|
+
|
329
|
+
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
|
330
|
+
value = send(reader_method, attribute_name)
|
331
|
+
value.duplicable? ? value.clone : value
|
332
|
+
rescue TypeError, NoMethodError
|
333
|
+
value
|
334
|
+
end
|
335
|
+
|
336
|
+
def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
|
337
|
+
arel_attributes_with_values(attributes_for_create(attribute_names))
|
338
|
+
end
|
339
|
+
|
340
|
+
def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
|
341
|
+
arel_attributes_with_values(attributes_for_update(attribute_names))
|
342
|
+
end
|
343
|
+
|
344
|
+
def attribute_method?(attr_name) # :nodoc:
|
345
|
+
# We check defined? because Syck calls respond_to? before actually calling initialize.
|
346
|
+
defined?(@attributes) && @attributes.include?(attr_name)
|
347
|
+
end
|
348
|
+
|
349
|
+
private
|
350
|
+
|
351
|
+
# Returns a Hash of the Arel::Attributes and attribute values that have been
|
352
|
+
# typecasted for use in an Arel insert/update method.
|
353
|
+
def arel_attributes_with_values(attribute_names)
|
354
|
+
attrs = {}
|
355
|
+
arel_table = self.class.arel_table
|
356
|
+
|
357
|
+
attribute_names.each do |name|
|
358
|
+
attrs[arel_table[name]] = typecasted_attribute_value(name)
|
359
|
+
end
|
360
|
+
attrs
|
361
|
+
end
|
362
|
+
|
363
|
+
# Filters the primary keys and readonly attributes from the attribute names.
|
364
|
+
def attributes_for_update(attribute_names)
|
365
|
+
attribute_names.select do |name|
|
366
|
+
column_for_attribute(name) && !readonly_attribute?(name)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Filters out the primary keys, from the attribute names, when the primary
|
371
|
+
# key is to be generated (e.g. the id attribute has no value).
|
372
|
+
def attributes_for_create(attribute_names)
|
373
|
+
attribute_names.select do |name|
|
374
|
+
column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def readonly_attribute?(name)
|
379
|
+
self.class.readonly_attributes.include?(name)
|
380
|
+
end
|
381
|
+
|
382
|
+
def pk_attribute?(name)
|
383
|
+
column_for_attribute(name).primary
|
384
|
+
end
|
385
|
+
|
386
|
+
def typecasted_attribute_value(name)
|
387
|
+
# FIXME: we need @attributes to be used consistently.
|
388
|
+
# If the values stored in @attributes were already typecasted, this code
|
389
|
+
# could be simplified
|
390
|
+
read_attribute(name)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|