activerecord 4.0.4 → 4.1.16
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 +4 -4
- data/CHANGELOG.md +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -1,6 +1,38 @@
|
|
1
|
+
require 'active_support/core_ext/module/method_transplanting'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeMethods
|
3
5
|
module Read
|
6
|
+
ReaderMethodCache = Class.new(AttributeMethodCache) {
|
7
|
+
private
|
8
|
+
# We want to generate the methods via module_eval rather than
|
9
|
+
# define_method, because define_method is slower on dispatch.
|
10
|
+
# Evaluating many similar methods may use more memory as the instruction
|
11
|
+
# sequences are duplicated and cached (in MRI). define_method may
|
12
|
+
# be slower on dispatch, but if you're careful about the closure
|
13
|
+
# created, then define_method will consume much less memory.
|
14
|
+
#
|
15
|
+
# But sometimes the database might return columns with
|
16
|
+
# characters that are not allowed in normal method names (like
|
17
|
+
# 'my_column(omg)'. So to work around this we first define with
|
18
|
+
# the __temp__ identifier, and then use alias method to rename
|
19
|
+
# it to what we want.
|
20
|
+
#
|
21
|
+
# We are also defining a constant to hold the frozen string of
|
22
|
+
# the attribute name. Using a constant means that we do not have
|
23
|
+
# to allocate an object on each call to the attribute method.
|
24
|
+
# Making it frozen means that it doesn't get duped when used to
|
25
|
+
# key the @attributes_cache in read_attribute.
|
26
|
+
def method_body(method_name, const_name)
|
27
|
+
<<-EOMETHOD
|
28
|
+
def #{method_name}
|
29
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
|
30
|
+
read_attribute(name) { |n| missing_attribute(n, caller) }
|
31
|
+
end
|
32
|
+
EOMETHOD
|
33
|
+
end
|
34
|
+
}.new
|
35
|
+
|
4
36
|
extend ActiveSupport::Concern
|
5
37
|
|
6
38
|
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
@@ -32,35 +64,30 @@ module ActiveRecord
|
|
32
64
|
|
33
65
|
protected
|
34
66
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
58
|
-
def __temp__#{safe_name}
|
59
|
-
read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
|
67
|
+
if Module.methods_transplantable?
|
68
|
+
def define_method_attribute(name)
|
69
|
+
method = ReaderMethodCache[name]
|
70
|
+
generated_attribute_methods.module_eval { define_method name, method }
|
71
|
+
end
|
72
|
+
else
|
73
|
+
def define_method_attribute(name)
|
74
|
+
safe_name = name.unpack('h*').first
|
75
|
+
temp_method = "__temp__#{safe_name}"
|
76
|
+
|
77
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
78
|
+
|
79
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
80
|
+
def #{temp_method}
|
81
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
82
|
+
read_attribute(name) { |n| missing_attribute(n, caller) }
|
83
|
+
end
|
84
|
+
STR
|
85
|
+
|
86
|
+
generated_attribute_methods.module_eval do
|
87
|
+
alias_method name, temp_method
|
88
|
+
undef_method temp_method
|
60
89
|
end
|
61
|
-
|
62
|
-
undef_method :__temp__#{safe_name}
|
63
|
-
STR
|
90
|
+
end
|
64
91
|
end
|
65
92
|
|
66
93
|
private
|
@@ -75,7 +102,7 @@ module ActiveRecord
|
|
75
102
|
end
|
76
103
|
|
77
104
|
# Returns the value of the attribute identified by <tt>attr_name</tt> after
|
78
|
-
# it has been typecast (for example, "2004-12-12" in a
|
105
|
+
# it has been typecast (for example, "2004-12-12" in a date column is cast
|
79
106
|
# to a date object, like Date.new(2004, 12, 12)).
|
80
107
|
def read_attribute(attr_name)
|
81
108
|
# If it's cached, just return it
|
@@ -24,10 +24,14 @@ module ActiveRecord
|
|
24
24
|
# serialized object must be of that class on retrieval or
|
25
25
|
# <tt>SerializationTypeMismatch</tt> will be raised.
|
26
26
|
#
|
27
|
+
# A notable side effect of serialized attributes is that the model will
|
28
|
+
# be updated on every save, even if it is not dirty.
|
29
|
+
#
|
27
30
|
# ==== Parameters
|
28
31
|
#
|
29
32
|
# * +attr_name+ - The field name that should be serialized.
|
30
|
-
# * +
|
33
|
+
# * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
|
34
|
+
# or a class name that the object type should be equal to.
|
31
35
|
#
|
32
36
|
# ==== Example
|
33
37
|
#
|
@@ -35,13 +39,28 @@ module ActiveRecord
|
|
35
39
|
# class User < ActiveRecord::Base
|
36
40
|
# serialize :preferences
|
37
41
|
# end
|
38
|
-
|
42
|
+
#
|
43
|
+
# # Serialize preferences using JSON as coder.
|
44
|
+
# class User < ActiveRecord::Base
|
45
|
+
# serialize :preferences, JSON
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # Serialize preferences as Hash using YAML coder.
|
49
|
+
# class User < ActiveRecord::Base
|
50
|
+
# serialize :preferences, Hash
|
51
|
+
# end
|
52
|
+
def serialize(attr_name, class_name_or_coder = Object)
|
39
53
|
include Behavior
|
40
54
|
|
41
|
-
|
42
|
-
|
55
|
+
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
56
|
+
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
57
|
+
# using the #as_json hook.
|
58
|
+
coder = if class_name_or_coder == ::JSON
|
59
|
+
Coders::JSON
|
60
|
+
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
61
|
+
class_name_or_coder
|
43
62
|
else
|
44
|
-
Coders::YAMLColumn.new(
|
63
|
+
Coders::YAMLColumn.new(class_name_or_coder)
|
45
64
|
end
|
46
65
|
|
47
66
|
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
|
@@ -50,13 +69,6 @@ module ActiveRecord
|
|
50
69
|
end
|
51
70
|
end
|
52
71
|
|
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
72
|
class Type # :nodoc:
|
61
73
|
def initialize(column)
|
62
74
|
@column = column
|
@@ -73,6 +85,10 @@ module ActiveRecord
|
|
73
85
|
def type
|
74
86
|
@column.type
|
75
87
|
end
|
88
|
+
|
89
|
+
def accessor
|
90
|
+
ActiveRecord::Store::IndifferentHashAccessor
|
91
|
+
end
|
76
92
|
end
|
77
93
|
|
78
94
|
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
|
@@ -115,6 +131,14 @@ module ActiveRecord
|
|
115
131
|
end
|
116
132
|
end
|
117
133
|
|
134
|
+
def should_record_timestamps?
|
135
|
+
super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
|
136
|
+
end
|
137
|
+
|
138
|
+
def keys_for_partial_write
|
139
|
+
super | (attributes.keys & self.class.serialized_attributes.keys)
|
140
|
+
end
|
141
|
+
|
118
142
|
def type_cast_attribute_for_write(column, value)
|
119
143
|
if column && coder = self.class.serialized_attributes[column.name]
|
120
144
|
Attribute.new(coder, value, :unserialized)
|
@@ -123,6 +147,14 @@ module ActiveRecord
|
|
123
147
|
end
|
124
148
|
end
|
125
149
|
|
150
|
+
def raw_type_cast_attribute_for_write(column, value)
|
151
|
+
if column && coder = self.class.serialized_attributes[column.name]
|
152
|
+
Attribute.new(coder, value, :serialized)
|
153
|
+
else
|
154
|
+
super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
126
158
|
def _field_changed?(attr, old, value)
|
127
159
|
if self.class.serialized_attributes.include?(attr)
|
128
160
|
old != value
|
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
34
34
|
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
35
35
|
method_body, line = <<-EOV, __LINE__ + 1
|
36
36
|
def #{attr_name}=(time)
|
37
|
-
time_with_zone =
|
37
|
+
time_with_zone = convert_value_to_time_zone("#{attr_name}", time)
|
38
38
|
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
39
39
|
write_attribute(:#{attr_name}, time)
|
40
40
|
#{attr_name}_will_change! if previous_time != time_with_zone
|
@@ -54,6 +54,18 @@ module ActiveRecord
|
|
54
54
|
(:datetime == column.type || :timestamp == column.type)
|
55
55
|
end
|
56
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def convert_value_to_time_zone(attr_name, value)
|
61
|
+
if value.is_a?(Array)
|
62
|
+
value.map { |v| convert_value_to_time_zone(attr_name, v) }
|
63
|
+
elsif value.respond_to?(:in_time_zone)
|
64
|
+
value.in_time_zone || self.class.columns_hash[attr_name].type_cast(value)
|
65
|
+
else
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
57
69
|
end
|
58
70
|
end
|
59
71
|
end
|
@@ -1,6 +1,21 @@
|
|
1
|
+
require 'active_support/core_ext/module/method_transplanting'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeMethods
|
3
5
|
module Write
|
6
|
+
WriterMethodCache = Class.new(AttributeMethodCache) {
|
7
|
+
private
|
8
|
+
|
9
|
+
def method_body(method_name, const_name)
|
10
|
+
<<-EOMETHOD
|
11
|
+
def #{method_name}(value)
|
12
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
|
13
|
+
write_attribute(name, value)
|
14
|
+
end
|
15
|
+
EOMETHOD
|
16
|
+
end
|
17
|
+
}.new
|
18
|
+
|
4
19
|
extend ActiveSupport::Concern
|
5
20
|
|
6
21
|
included do
|
@@ -10,19 +25,29 @@ module ActiveRecord
|
|
10
25
|
module ClassMethods
|
11
26
|
protected
|
12
27
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
28
|
+
if Module.methods_transplantable?
|
29
|
+
# See define_method_attribute in read.rb for an explanation of
|
30
|
+
# this code.
|
31
|
+
def define_method_attribute=(name)
|
32
|
+
method = WriterMethodCache[name]
|
33
|
+
generated_attribute_methods.module_eval {
|
34
|
+
define_method "#{name}=", method
|
35
|
+
}
|
36
|
+
end
|
37
|
+
else
|
38
|
+
def define_method_attribute=(name)
|
39
|
+
safe_name = name.unpack('h*').first
|
40
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
18
41
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
42
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
43
|
+
def __temp__#{safe_name}=(value)
|
44
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
45
|
+
write_attribute(name, value)
|
46
|
+
end
|
47
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
48
|
+
undef_method :__temp__#{safe_name}=
|
49
|
+
STR
|
50
|
+
end
|
26
51
|
end
|
27
52
|
end
|
28
53
|
|
@@ -30,6 +55,27 @@ module ActiveRecord
|
|
30
55
|
# specified +value+. Empty strings for fixnum and float columns are
|
31
56
|
# turned into +nil+.
|
32
57
|
def write_attribute(attr_name, value)
|
58
|
+
write_attribute_with_type_cast(attr_name, value, :type_cast_attribute_for_write)
|
59
|
+
end
|
60
|
+
|
61
|
+
def raw_write_attribute(attr_name, value)
|
62
|
+
write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
# Handle *= for method_missing.
|
67
|
+
def attribute=(attribute_name, value)
|
68
|
+
write_attribute(attribute_name, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
def type_cast_attribute_for_write(column, value)
|
72
|
+
return value unless column
|
73
|
+
|
74
|
+
column.type_cast_for_write value
|
75
|
+
end
|
76
|
+
alias_method :raw_type_cast_attribute_for_write, :type_cast_attribute_for_write
|
77
|
+
|
78
|
+
def write_attribute_with_type_cast(attr_name, value, type_cast_method)
|
33
79
|
attr_name = attr_name.to_s
|
34
80
|
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
35
81
|
@attributes_cache.delete(attr_name)
|
@@ -42,24 +88,11 @@ module ActiveRecord
|
|
42
88
|
end
|
43
89
|
|
44
90
|
if column || @attributes.has_key?(attr_name)
|
45
|
-
@attributes[attr_name] =
|
91
|
+
@attributes[attr_name] = send(type_cast_method, column, value)
|
46
92
|
else
|
47
93
|
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
48
94
|
end
|
49
95
|
end
|
50
|
-
alias_method :raw_write_attribute, :write_attribute
|
51
|
-
|
52
|
-
private
|
53
|
-
# Handle *= for method_missing.
|
54
|
-
def attribute=(attribute_name, value)
|
55
|
-
write_attribute(attribute_name, value)
|
56
|
-
end
|
57
|
-
|
58
|
-
def type_cast_attribute_for_write(column, value)
|
59
|
-
return value unless column
|
60
|
-
|
61
|
-
column.type_cast_for_write value
|
62
|
-
end
|
63
96
|
end
|
64
97
|
end
|
65
98
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_support/core_ext/enumerable'
|
2
2
|
require 'mutex_m'
|
3
|
+
require 'thread_safe'
|
3
4
|
|
4
5
|
module ActiveRecord
|
5
6
|
# = Active Record Attribute Methods
|
@@ -19,6 +20,39 @@ module ActiveRecord
|
|
19
20
|
include Serialization
|
20
21
|
end
|
21
22
|
|
23
|
+
AttrNames = Module.new {
|
24
|
+
def self.set_name_cache(name, value)
|
25
|
+
const_name = "ATTR_#{name}"
|
26
|
+
unless const_defined? const_name
|
27
|
+
const_set const_name, value.dup.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
}
|
31
|
+
|
32
|
+
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
33
|
+
|
34
|
+
class AttributeMethodCache
|
35
|
+
def initialize
|
36
|
+
@module = Module.new
|
37
|
+
@method_cache = ThreadSafe::Cache.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](name)
|
41
|
+
@method_cache.compute_if_absent(name) do
|
42
|
+
safe_name = name.unpack('h*').first
|
43
|
+
temp_method = "__temp__#{safe_name}"
|
44
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
45
|
+
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
|
46
|
+
@module.instance_method temp_method
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def method_body; raise NotImplementedError; end
|
52
|
+
end
|
53
|
+
|
54
|
+
class GeneratedAttributeMethods < Module; end # :nodoc:
|
55
|
+
|
22
56
|
module ClassMethods
|
23
57
|
def inherited(child_class) #:nodoc:
|
24
58
|
child_class.initialize_generated_modules
|
@@ -26,25 +60,17 @@ module ActiveRecord
|
|
26
60
|
end
|
27
61
|
|
28
62
|
def initialize_generated_modules # :nodoc:
|
29
|
-
@generated_attribute_methods =
|
30
|
-
extend Mutex_m
|
31
|
-
|
32
|
-
const_set :AttrNames, Module.new {
|
33
|
-
def self.set_name_cache(name, value)
|
34
|
-
const_name = "ATTR_#{name}"
|
35
|
-
unless const_defined? const_name
|
36
|
-
const_set const_name, value.dup.freeze
|
37
|
-
end
|
38
|
-
end
|
39
|
-
}
|
40
|
-
}
|
63
|
+
@generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
|
41
64
|
@attribute_methods_generated = false
|
42
65
|
include @generated_attribute_methods
|
66
|
+
|
67
|
+
super
|
43
68
|
end
|
44
69
|
|
45
70
|
# Generates all the attribute related methods for columns in the database
|
46
71
|
# accessors, mutators and query methods.
|
47
72
|
def define_attribute_methods # :nodoc:
|
73
|
+
return false if @attribute_methods_generated
|
48
74
|
# Use a mutex; we don't want two thread simultaneously trying to define
|
49
75
|
# attribute methods.
|
50
76
|
generated_attribute_methods.synchronize do
|
@@ -58,7 +84,7 @@ module ActiveRecord
|
|
58
84
|
|
59
85
|
def undefine_attribute_methods # :nodoc:
|
60
86
|
generated_attribute_methods.synchronize do
|
61
|
-
super if @attribute_methods_generated
|
87
|
+
super if defined?(@attribute_methods_generated) && @attribute_methods_generated
|
62
88
|
@attribute_methods_generated = false
|
63
89
|
end
|
64
90
|
end
|
@@ -79,29 +105,48 @@ module ActiveRecord
|
|
79
105
|
# # => false
|
80
106
|
def instance_method_already_implemented?(method_name)
|
81
107
|
if dangerous_attribute_method?(method_name)
|
82
|
-
raise DangerousAttributeError, "#{method_name} is defined by Active Record"
|
108
|
+
raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
|
83
109
|
end
|
84
110
|
|
85
111
|
if superclass == Base
|
86
112
|
super
|
87
113
|
else
|
88
|
-
# If
|
89
|
-
|
90
|
-
|
91
|
-
|
114
|
+
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
|
115
|
+
# defines its own attribute method, then we don't want to overwrite that.
|
116
|
+
defined = method_defined_within?(method_name, superclass, Base) &&
|
117
|
+
! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
|
118
|
+
defined || super
|
92
119
|
end
|
93
120
|
end
|
94
121
|
|
95
|
-
# A method name is 'dangerous' if it is already defined by Active Record, but
|
122
|
+
# A method name is 'dangerous' if it is already (re)defined by Active Record, but
|
96
123
|
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
97
124
|
def dangerous_attribute_method?(name) # :nodoc:
|
98
125
|
method_defined_within?(name, Base)
|
99
126
|
end
|
100
127
|
|
101
|
-
def method_defined_within?(name, klass,
|
128
|
+
def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
102
129
|
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
103
|
-
if
|
104
|
-
klass.instance_method(name).owner !=
|
130
|
+
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
|
131
|
+
klass.instance_method(name).owner != superklass.instance_method(name).owner
|
132
|
+
else
|
133
|
+
true
|
134
|
+
end
|
135
|
+
else
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
|
141
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
|
142
|
+
def dangerous_class_method?(method_name)
|
143
|
+
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
|
144
|
+
end
|
145
|
+
|
146
|
+
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
|
147
|
+
if klass.respond_to?(name, true)
|
148
|
+
if superklass.respond_to?(name, true)
|
149
|
+
klass.method(name).owner != superklass.method(name).owner
|
105
150
|
else
|
106
151
|
true
|
107
152
|
end
|
@@ -161,6 +206,7 @@ module ActiveRecord
|
|
161
206
|
# this is probably horribly slow, but should only happen at most once for a given AR class
|
162
207
|
attribute_method.bind(self).call(*args, &block)
|
163
208
|
else
|
209
|
+
return super unless respond_to_missing?(method, true)
|
164
210
|
send(method, *args, &block)
|
165
211
|
end
|
166
212
|
else
|
@@ -168,20 +214,6 @@ module ActiveRecord
|
|
168
214
|
end
|
169
215
|
end
|
170
216
|
|
171
|
-
def attribute_missing(match, *args, &block) # :nodoc:
|
172
|
-
if self.class.columns_hash[match.attr_name]
|
173
|
-
ActiveSupport::Deprecation.warn(
|
174
|
-
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
|
175
|
-
"dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
|
176
|
-
"is a column of the table. If this error has happened through normal usage of Active " \
|
177
|
-
"Record (rather than through your own code or external libraries), please report it as " \
|
178
|
-
"a bug."
|
179
|
-
)
|
180
|
-
end
|
181
|
-
|
182
|
-
super
|
183
|
-
end
|
184
|
-
|
185
217
|
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
186
218
|
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
|
187
219
|
# which will all return +true+. It also define the attribute methods if they have
|
@@ -210,7 +242,7 @@ module ActiveRecord
|
|
210
242
|
# For queries selecting a subset of columns, return false for unselected columns.
|
211
243
|
# We check defined?(@attributes) not to issue warnings if called on objects that
|
212
244
|
# have been allocated but not yet initialized.
|
213
|
-
if defined?(@attributes) && @attributes.
|
245
|
+
if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
|
214
246
|
return has_attribute?(name)
|
215
247
|
end
|
216
248
|
|
@@ -263,24 +295,31 @@ module ActiveRecord
|
|
263
295
|
|
264
296
|
# Returns an <tt>#inspect</tt>-like string for the value of the
|
265
297
|
# attribute +attr_name+. String attributes are truncated upto 50
|
266
|
-
# characters,
|
267
|
-
# <tt>:db</tt> format
|
268
|
-
# <tt>#inspect</tt> without
|
298
|
+
# characters, Date and Time attributes are returned in the
|
299
|
+
# <tt>:db</tt> format, Array attributes are truncated upto 10 values.
|
300
|
+
# Other attributes return the value of <tt>#inspect</tt> without
|
301
|
+
# modification.
|
269
302
|
#
|
270
303
|
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
|
271
304
|
#
|
272
305
|
# person.attribute_for_inspect(:name)
|
273
|
-
# # => "\"David Heinemeier Hansson David Heinemeier Hansson
|
306
|
+
# # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
|
274
307
|
#
|
275
308
|
# person.attribute_for_inspect(:created_at)
|
276
309
|
# # => "\"2012-10-22 00:15:07\""
|
310
|
+
#
|
311
|
+
# person.attribute_for_inspect(:tag_ids)
|
312
|
+
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
|
277
313
|
def attribute_for_inspect(attr_name)
|
278
314
|
value = read_attribute(attr_name)
|
279
315
|
|
280
316
|
if value.is_a?(String) && value.length > 50
|
281
|
-
"#{value[0
|
317
|
+
"#{value[0, 50]}...".inspect
|
282
318
|
elsif value.is_a?(Date) || value.is_a?(Time)
|
283
319
|
%("#{value.to_s(:db)}")
|
320
|
+
elsif value.is_a?(Array) && value.size > 10
|
321
|
+
inspected = value.first(10).inspect
|
322
|
+
%(#{inspected[0...-1]}, ...])
|
284
323
|
else
|
285
324
|
value.inspect
|
286
325
|
end
|
@@ -324,7 +363,7 @@ module ActiveRecord
|
|
324
363
|
end
|
325
364
|
|
326
365
|
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
327
|
-
# "2004-12-12" in a
|
366
|
+
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
|
328
367
|
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
|
329
368
|
#
|
330
369
|
# Alias for the <tt>read_attribute</tt> method.
|