activerecord 4.1.0 → 4.2.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.

Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,36 +1,23 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Serialization
4
6
  extend ActiveSupport::Concern
5
7
 
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
8
  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
9
  # If you have an attribute that needs to be saved to the database as an
21
10
  # object, and retrieved as the same object, then specify the name of that
22
11
  # attribute using this method and it will be handled automatically. The
23
12
  # 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
- # A notable side effect of serialized attributes is that the model will
28
- # be updated on every save, even if it is not dirty.
13
+ # serialized object must be of that class on assignment and retrieval.
14
+ # Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
29
15
  #
30
16
  # ==== Parameters
31
17
  #
32
18
  # * +attr_name+ - The field name that should be serialized.
33
- # * +class_name+ - Optional, class name that the object type should be equal to.
19
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
20
+ # or a class name that the object type should be equal to.
34
21
  #
35
22
  # ==== Example
36
23
  #
@@ -38,141 +25,44 @@ module ActiveRecord
38
25
  # class User < ActiveRecord::Base
39
26
  # serialize :preferences
40
27
  # end
41
- def serialize(attr_name, class_name = Object)
42
- include Behavior
43
-
44
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
45
- class_name
28
+ #
29
+ # # Serialize preferences using JSON as coder.
30
+ # class User < ActiveRecord::Base
31
+ # serialize :preferences, JSON
32
+ # end
33
+ #
34
+ # # Serialize preferences as Hash using YAML coder.
35
+ # class User < ActiveRecord::Base
36
+ # serialize :preferences, Hash
37
+ # end
38
+ def serialize(attr_name, class_name_or_coder = Object)
39
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
40
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
41
+ # using the #as_json hook.
42
+ coder = if class_name_or_coder == ::JSON
43
+ Coders::JSON
44
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
45
+ class_name_or_coder
46
46
  else
47
- Coders::YAMLColumn.new(class_name)
47
+ Coders::YAMLColumn.new(class_name_or_coder)
48
48
  end
49
49
 
50
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
51
- # has its own hash of own serialized attributes
52
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
53
- end
54
- end
55
-
56
- class Type # :nodoc:
57
- def initialize(column)
58
- @column = column
59
- end
60
-
61
- def type_cast(value)
62
- if value.state == :serialized
63
- value.unserialized_value @column.type_cast value.value
64
- else
65
- value.unserialized_value
50
+ decorate_attribute_type(attr_name, :serialize) do |type|
51
+ Type::Serialized.new(type, coder)
66
52
  end
67
53
  end
68
54
 
69
- def type
70
- @column.type
71
- end
72
-
73
- def accessor
74
- ActiveRecord::Store::IndifferentHashAccessor
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
55
+ def serialized_attributes
56
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
57
+ `serialized_attributes` is deprecated without replacement, and will
58
+ be removed in Rails 5.0.
59
+ MSG
86
60
 
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 should_record_timestamps?
119
- super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
120
- end
121
-
122
- def keys_for_partial_write
123
- super | (attributes.keys & self.class.serialized_attributes.keys)
124
- end
125
-
126
- def type_cast_attribute_for_write(column, value)
127
- if column && coder = self.class.serialized_attributes[column.name]
128
- Attribute.new(coder, value, :unserialized)
129
- else
130
- super
131
- end
132
- end
133
-
134
- def _field_changed?(attr, old, value)
135
- if self.class.serialized_attributes.include?(attr)
136
- old != value
137
- else
138
- super
139
- end
140
- end
141
-
142
- def read_attribute_before_type_cast(attr_name)
143
- if self.class.serialized_attributes.include?(attr_name)
144
- super.unserialized_value
145
- else
146
- super
147
- end
148
- end
149
-
150
- def attributes_before_type_cast
151
- super.dup.tap do |attributes|
152
- self.class.serialized_attributes.each_key do |key|
153
- if attributes.key?(key)
154
- attributes[key] = attributes[key].unserialized_value
155
- end
156
- end
157
- end
158
- end
159
-
160
- def typecasted_attribute_value(name)
161
- if self.class.serialized_attributes.include?(name)
162
- @attributes[name].serialized_value
163
- else
164
- super
165
- end
166
- end
167
-
168
- def attributes_for_coder
169
- attribute_names.each_with_object({}) do |name, attrs|
170
- attrs[name] = if self.class.serialized_attributes.include?(name)
171
- @attributes[name].serialized_value
172
- else
173
- read_attribute(name)
174
- end
175
- end
61
+ @serialized_attributes ||= Hash[
62
+ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
63
+ [c.name, c.cast_type.coder]
64
+ }
65
+ ]
176
66
  end
177
67
  end
178
68
  end
@@ -1,18 +1,29 @@
1
1
  module ActiveRecord
2
2
  module AttributeMethods
3
3
  module TimeZoneConversion
4
- class Type # :nodoc:
5
- def initialize(column)
6
- @column = column
4
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
5
+ include Type::Decorator
6
+
7
+ def type_cast_from_database(value)
8
+ convert_time_to_time_zone(super)
7
9
  end
8
10
 
9
- def type_cast(value)
10
- value = @column.type_cast(value)
11
- value.acts_like?(:time) ? value.in_time_zone : value
11
+ def type_cast_from_user(value)
12
+ if value.is_a?(Array)
13
+ value.map { |v| type_cast_from_user(v) }
14
+ elsif value.respond_to?(:in_time_zone)
15
+ value.in_time_zone || super
16
+ end
12
17
  end
13
18
 
14
- def type
15
- @column.type
19
+ def convert_time_to_time_zone(value)
20
+ if value.is_a?(Array)
21
+ value.map { |v| convert_time_to_time_zone(v) }
22
+ elsif value.acts_like?(:time)
23
+ value.in_time_zone
24
+ else
25
+ value
26
+ end
16
27
  end
17
28
  end
18
29
 
@@ -27,31 +38,26 @@ module ActiveRecord
27
38
  end
28
39
 
29
40
  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
41
+ private
42
+
43
+ def inherited(subclass)
44
+ # We need to apply this decorator here, rather than on module inclusion. The closure
45
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
46
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
47
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
48
+ subclass.class_eval do
49
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
50
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
51
+ TimeZoneConverter.new(type)
52
+ end
47
53
  end
54
+ super
48
55
  end
49
56
 
50
- private
51
- def create_time_zone_conversion_attribute?(name, column)
57
+ def create_time_zone_conversion_attribute?(name, cast_type)
52
58
  time_zone_aware_attributes &&
53
59
  !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
- (:datetime == column.type || :timestamp == column.type)
60
+ (:datetime == cast_type.type)
55
61
  end
56
62
  end
57
63
  end
@@ -26,8 +26,6 @@ module ActiveRecord
26
26
  protected
27
27
 
28
28
  if Module.methods_transplantable?
29
- # See define_method_attribute in read.rb for an explanation of
30
- # this code.
31
29
  def define_method_attribute=(name)
32
30
  method = WriterMethodCache[name]
33
31
  generated_attribute_methods.module_eval {
@@ -55,24 +53,12 @@ module ActiveRecord
55
53
  # specified +value+. Empty strings for fixnum and float columns are
56
54
  # turned into +nil+.
57
55
  def write_attribute(attr_name, value)
58
- attr_name = attr_name.to_s
59
- attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
60
- @attributes_cache.delete(attr_name)
61
- column = column_for_attribute(attr_name)
62
-
63
- # If we're dealing with a binary column, write the data to the cache
64
- # so we don't attempt to typecast multiple times.
65
- if column && column.binary?
66
- @attributes_cache[attr_name] = value
67
- end
56
+ write_attribute_with_type_cast(attr_name, value, true)
57
+ end
68
58
 
69
- if column || @attributes.has_key?(attr_name)
70
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
71
- else
72
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
73
- end
59
+ def raw_write_attribute(attr_name, value)
60
+ write_attribute_with_type_cast(attr_name, value, false)
74
61
  end
75
- alias_method :raw_write_attribute, :write_attribute
76
62
 
77
63
  private
78
64
  # Handle *= for method_missing.
@@ -80,10 +66,17 @@ module ActiveRecord
80
66
  write_attribute(attribute_name, value)
81
67
  end
82
68
 
83
- def type_cast_attribute_for_write(column, value)
84
- return value unless column
69
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
70
+ attr_name = attr_name.to_s
71
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
72
+
73
+ if should_type_cast
74
+ @attributes.write_from_user(attr_name, value)
75
+ else
76
+ @attributes.write_cast_value(attr_name, value)
77
+ end
85
78
 
86
- column.type_cast_for_write value
79
+ value
87
80
  end
88
81
  end
89
82
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/enumerable'
2
+ require 'active_support/core_ext/string/filters'
2
3
  require 'mutex_m'
3
4
  require 'thread_safe'
4
5
 
@@ -18,6 +19,8 @@ module ActiveRecord
18
19
  include TimeZoneConversion
19
20
  include Dirty
20
21
  include Serialization
22
+
23
+ delegate :column_for_attribute, to: :class
21
24
  end
22
25
 
23
26
  AttrNames = Module.new {
@@ -29,7 +32,7 @@ module ActiveRecord
29
32
  end
30
33
  }
31
34
 
32
- BLACKLISTED_CLASS_METHODS = %w(private public protected)
35
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
33
36
 
34
37
  class AttributeMethodCache
35
38
  def initialize
@@ -48,9 +51,15 @@ module ActiveRecord
48
51
  end
49
52
 
50
53
  private
51
- def method_body; raise NotImplementedError; end
54
+
55
+ # Override this method in the subclasses for method body.
56
+ def method_body(method_name, const_name)
57
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
58
+ end
52
59
  end
53
60
 
61
+ class GeneratedAttributeMethods < Module; end # :nodoc:
62
+
54
63
  module ClassMethods
55
64
  def inherited(child_class) #:nodoc:
56
65
  child_class.initialize_generated_modules
@@ -58,15 +67,18 @@ module ActiveRecord
58
67
  end
59
68
 
60
69
  def initialize_generated_modules # :nodoc:
61
- @generated_attribute_methods = Module.new { extend Mutex_m }
70
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
62
71
  @attribute_methods_generated = false
63
72
  include @generated_attribute_methods
73
+
74
+ super
64
75
  end
65
76
 
66
77
  # Generates all the attribute related methods for columns in the database
67
78
  # accessors, mutators and query methods.
68
79
  def define_attribute_methods # :nodoc:
69
- # Use a mutex; we don't want two thread simultaneously trying to define
80
+ return false if @attribute_methods_generated
81
+ # Use a mutex; we don't want two threads simultaneously trying to define
70
82
  # attribute methods.
71
83
  generated_attribute_methods.synchronize do
72
84
  return false if @attribute_methods_generated
@@ -79,7 +91,7 @@ module ActiveRecord
79
91
 
80
92
  def undefine_attribute_methods # :nodoc:
81
93
  generated_attribute_methods.synchronize do
82
- super if @attribute_methods_generated
94
+ super if defined?(@attribute_methods_generated) && @attribute_methods_generated
83
95
  @attribute_methods_generated = false
84
96
  end
85
97
  end
@@ -100,16 +112,17 @@ module ActiveRecord
100
112
  # # => false
101
113
  def instance_method_already_implemented?(method_name)
102
114
  if dangerous_attribute_method?(method_name)
103
- raise DangerousAttributeError, "#{method_name} is defined by Active Record"
115
+ 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."
104
116
  end
105
117
 
106
118
  if superclass == Base
107
119
  super
108
120
  else
109
- # If B < A and A defines its own attribute method, then we don't want to overwrite that.
110
- defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
111
- base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
112
- defined && !base_defined || super
121
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
122
+ # defines its own attribute method, then we don't want to overwrite that.
123
+ defined = method_defined_within?(method_name, superclass, Base) &&
124
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
125
+ defined || super
113
126
  end
114
127
  end
115
128
 
@@ -149,16 +162,6 @@ module ActiveRecord
149
162
  end
150
163
  end
151
164
 
152
- def find_generated_attribute_method(method_name) # :nodoc:
153
- klass = self
154
- until klass == Base
155
- gen_methods = klass.generated_attribute_methods
156
- return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
157
- klass = klass.superclass
158
- end
159
- nil
160
- end
161
-
162
165
  # Returns +true+ if +attribute+ is an attribute method and table exists,
163
166
  # +false+ otherwise.
164
167
  #
@@ -187,23 +190,29 @@ module ActiveRecord
187
190
  []
188
191
  end
189
192
  end
190
- end
191
193
 
192
- # If we haven't generated any methods yet, generate them, then
193
- # see if we've created the method we're looking for.
194
- def method_missing(method, *args, &block) # :nodoc:
195
- self.class.define_attribute_methods
196
- if respond_to_without_attributes?(method)
197
- # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
198
- # call in a overwritten attribute method
199
- if attribute_method = self.class.find_generated_attribute_method(method)
200
- # this is probably horribly slow, but should only happen at most once for a given AR class
201
- attribute_method.bind(self).call(*args, &block)
202
- else
203
- send(method, *args, &block)
194
+ # Returns the column object for the named attribute.
195
+ # Returns nil if the named attribute does not exist.
196
+ #
197
+ # class Person < ActiveRecord::Base
198
+ # end
199
+ #
200
+ # person = Person.new
201
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
202
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
203
+ #
204
+ # person.column_for_attribute(:nothing)
205
+ # # => nil
206
+ def column_for_attribute(name)
207
+ column = columns_hash[name.to_s]
208
+ if column.nil?
209
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
210
+ `#column_for_attribute` will return a null object for non-existent
211
+ columns in Rails 5. Use `#has_attribute?` if you need to check for
212
+ an attribute's existence.
213
+ MSG
204
214
  end
205
- else
206
- super
215
+ column
207
216
  end
208
217
  end
209
218
 
@@ -224,18 +233,14 @@ module ActiveRecord
224
233
  # person.respond_to('age?') # => true
225
234
  # person.respond_to(:nothing) # => false
226
235
  def respond_to?(name, include_private = false)
236
+ return false unless super
227
237
  name = name.to_s
228
- self.class.define_attribute_methods
229
- result = super
230
-
231
- # If the result is false the answer is false.
232
- return false unless result
233
238
 
234
239
  # If the result is true then check for the select case.
235
240
  # For queries selecting a subset of columns, return false for unselected columns.
236
241
  # We check defined?(@attributes) not to issue warnings if called on objects that
237
242
  # have been allocated but not yet initialized.
238
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
243
+ if defined?(@attributes) && self.class.column_names.include?(name)
239
244
  return has_attribute?(name)
240
245
  end
241
246
 
@@ -252,7 +257,7 @@ module ActiveRecord
252
257
  # person.has_attribute?('age') # => true
253
258
  # person.has_attribute?(:nothing) # => false
254
259
  def has_attribute?(attr_name)
255
- @attributes.has_key?(attr_name.to_s)
260
+ @attributes.key?(attr_name.to_s)
256
261
  end
257
262
 
258
263
  # Returns an array of names for the attributes available on this object.
@@ -276,20 +281,13 @@ module ActiveRecord
276
281
  # person.attributes
277
282
  # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
278
283
  def attributes
279
- attribute_names.each_with_object({}) { |name, attrs|
280
- attrs[name] = read_attribute(name)
281
- }
282
- end
283
-
284
- # Placeholder so it can be overriden when needed by serialization
285
- def attributes_for_coder # :nodoc:
286
- attributes
284
+ @attributes.to_hash
287
285
  end
288
286
 
289
287
  # Returns an <tt>#inspect</tt>-like string for the value of the
290
- # attribute +attr_name+. String attributes are truncated upto 50
288
+ # attribute +attr_name+. String attributes are truncated up to 50
291
289
  # characters, Date and Time attributes are returned in the
292
- # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
290
+ # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
293
291
  # Other attributes return the value of <tt>#inspect</tt> without
294
292
  # modification.
295
293
  #
@@ -326,39 +324,24 @@ module ActiveRecord
326
324
  # class Task < ActiveRecord::Base
327
325
  # end
328
326
  #
329
- # person = Task.new(title: '', is_done: false)
330
- # person.attribute_present?(:title) # => false
331
- # person.attribute_present?(:is_done) # => true
332
- # person.name = 'Francesco'
333
- # person.is_done = true
334
- # person.attribute_present?(:title) # => true
335
- # person.attribute_present?(:is_done) # => true
327
+ # task = Task.new(title: '', is_done: false)
328
+ # task.attribute_present?(:title) # => false
329
+ # task.attribute_present?(:is_done) # => true
330
+ # task.title = 'Buy milk'
331
+ # task.is_done = true
332
+ # task.attribute_present?(:title) # => true
333
+ # task.attribute_present?(:is_done) # => true
336
334
  def attribute_present?(attribute)
337
- value = read_attribute(attribute)
335
+ value = _read_attribute(attribute)
338
336
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
339
337
  end
340
338
 
341
- # Returns the column object for the named attribute. Returns +nil+ if the
342
- # named attribute not exists.
343
- #
344
- # class Person < ActiveRecord::Base
345
- # end
346
- #
347
- # person = Person.new
348
- # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
349
- # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
350
- #
351
- # person.column_for_attribute(:nothing)
352
- # # => nil
353
- def column_for_attribute(name)
354
- # FIXME: should this return a null object for columns that don't exist?
355
- self.class.columns_hash[name.to_s]
356
- end
357
-
358
339
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
359
340
  # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
360
341
  # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
361
342
  #
343
+ # Note: +:id+ is always present.
344
+ #
362
345
  # Alias for the <tt>read_attribute</tt> method.
363
346
  #
364
347
  # class Person < ActiveRecord::Base
@@ -392,13 +375,6 @@ module ActiveRecord
392
375
 
393
376
  protected
394
377
 
395
- def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
396
- attribute_names.each do |name|
397
- attributes[name] = clone_attribute_value(reader_method, name)
398
- end
399
- attributes
400
- end
401
-
402
378
  def clone_attribute_value(reader_method, attribute_name) # :nodoc:
403
379
  value = send(reader_method, attribute_name)
404
380
  value.duplicable? ? value.clone : value
@@ -416,7 +392,7 @@ module ActiveRecord
416
392
 
417
393
  def attribute_method?(attr_name) # :nodoc:
418
394
  # We check defined? because Syck calls respond_to? before actually calling initialize.
419
- defined?(@attributes) && @attributes.include?(attr_name)
395
+ defined?(@attributes) && @attributes.key?(attr_name)
420
396
  end
421
397
 
422
398
  private
@@ -435,16 +411,16 @@ module ActiveRecord
435
411
 
436
412
  # Filters the primary keys and readonly attributes from the attribute names.
437
413
  def attributes_for_update(attribute_names)
438
- attribute_names.select do |name|
439
- column_for_attribute(name) && !readonly_attribute?(name)
414
+ attribute_names.reject do |name|
415
+ readonly_attribute?(name)
440
416
  end
441
417
  end
442
418
 
443
419
  # Filters out the primary keys, from the attribute names, when the primary
444
420
  # key is to be generated (e.g. the id attribute has no value).
445
421
  def attributes_for_create(attribute_names)
446
- attribute_names.select do |name|
447
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
422
+ attribute_names.reject do |name|
423
+ pk_attribute?(name) && id.nil?
448
424
  end
449
425
  end
450
426
 
@@ -453,14 +429,11 @@ module ActiveRecord
453
429
  end
454
430
 
455
431
  def pk_attribute?(name)
456
- column_for_attribute(name).primary
432
+ name == self.class.primary_key
457
433
  end
458
434
 
459
435
  def typecasted_attribute_value(name)
460
- # FIXME: we need @attributes to be used consistently.
461
- # If the values stored in @attributes were already typecasted, this code
462
- # could be simplified
463
- read_attribute(name)
436
+ _read_attribute(name)
464
437
  end
465
438
  end
466
439
  end