activerecord 4.1.16 → 4.2.11.3

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 +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  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/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,31 +1,17 @@
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
  #
@@ -50,8 +36,6 @@ module ActiveRecord
50
36
  # serialize :preferences, Hash
51
37
  # end
52
38
  def serialize(attr_name, class_name_or_coder = Object)
53
- include Behavior
54
-
55
39
  # When ::JSON is used, force it to go through the Active Support JSON encoder
56
40
  # to ensure special objects (e.g. Active Record models) are dumped correctly
57
41
  # using the #as_json hook.
@@ -63,140 +47,22 @@ module ActiveRecord
63
47
  Coders::YAMLColumn.new(class_name_or_coder)
64
48
  end
65
49
 
66
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
67
- # has its own hash of own serialized attributes
68
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
69
- end
70
- end
71
-
72
- class Type # :nodoc:
73
- def initialize(column)
74
- @column = column
75
- end
76
-
77
- def type_cast(value)
78
- if value.state == :serialized
79
- value.unserialized_value @column.type_cast value.value
80
- else
81
- value.unserialized_value
82
- end
83
- end
84
-
85
- def type
86
- @column.type
87
- end
88
-
89
- def accessor
90
- ActiveRecord::Store::IndifferentHashAccessor
91
- end
92
- end
93
-
94
- class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
95
- def unserialized_value(v = value)
96
- state == :serialized ? unserialize(v) : value
97
- end
98
-
99
- def serialized_value
100
- state == :unserialized ? serialize : value
101
- end
102
-
103
- def unserialize(v)
104
- self.state = :unserialized
105
- self.value = coder.load(v)
106
- end
107
-
108
- def serialize
109
- self.state = :serialized
110
- self.value = coder.dump(value)
111
- end
112
- end
113
-
114
- # This is only added to the model when serialize is called, which
115
- # ensures we do not make things slower when serialization is not used.
116
- module Behavior # :nodoc:
117
- extend ActiveSupport::Concern
118
-
119
- module ClassMethods # :nodoc:
120
- def initialize_attributes(attributes, options = {})
121
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
122
- super(attributes, options)
123
-
124
- serialized_attributes.each do |key, coder|
125
- if attributes.key?(key)
126
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
127
- end
128
- end
129
-
130
- attributes
131
- end
132
- end
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
-
142
- def type_cast_attribute_for_write(column, value)
143
- if column && coder = self.class.serialized_attributes[column.name]
144
- Attribute.new(coder, value, :unserialized)
145
- else
146
- super
50
+ decorate_attribute_type(attr_name, :serialize) do |type|
51
+ Type::Serialized.new(type, coder)
147
52
  end
148
53
  end
149
54
 
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
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
157
60
 
158
- def _field_changed?(attr, old, value)
159
- if self.class.serialized_attributes.include?(attr)
160
- old != value
161
- else
162
- super
163
- end
164
- end
165
-
166
- def read_attribute_before_type_cast(attr_name)
167
- if self.class.serialized_attributes.include?(attr_name)
168
- super.unserialized_value
169
- else
170
- super
171
- end
172
- end
173
-
174
- def attributes_before_type_cast
175
- super.dup.tap do |attributes|
176
- self.class.serialized_attributes.each_key do |key|
177
- if attributes.key?(key)
178
- attributes[key] = attributes[key].unserialized_value
179
- end
180
- end
181
- end
182
- end
183
-
184
- def typecasted_attribute_value(name)
185
- if self.class.serialized_attributes.include?(name)
186
- @attributes[name].serialized_value
187
- else
188
- super
189
- end
190
- end
191
-
192
- def attributes_for_coder
193
- attribute_names.each_with_object({}) do |name, attrs|
194
- attrs[name] = if self.class.serialized_attributes.include?(name)
195
- @attributes[name].serialized_value
196
- else
197
- read_attribute(name)
198
- end
199
- 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
+ ]
200
66
  end
201
67
  end
202
68
  end
@@ -1,18 +1,33 @@
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
+ begin
16
+ value.in_time_zone || super
17
+ rescue ArgumentError
18
+ nil
19
+ end
20
+ end
12
21
  end
13
22
 
14
- def type
15
- @column.type
23
+ def convert_time_to_time_zone(value)
24
+ if value.is_a?(Array)
25
+ value.map { |v| convert_time_to_time_zone(v) }
26
+ elsif value.acts_like?(:time)
27
+ value.in_time_zone
28
+ else
29
+ value
30
+ end
16
31
  end
17
32
  end
18
33
 
@@ -27,43 +42,26 @@ module ActiveRecord
27
42
  end
28
43
 
29
44
  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 = convert_value_to_time_zone("#{attr_name}", time)
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
45
+ private
46
+
47
+ def inherited(subclass)
48
+ # We need to apply this decorator here, rather than on module inclusion. The closure
49
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
50
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
51
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
52
+ subclass.class_eval do
53
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
54
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
55
+ TimeZoneConverter.new(type)
56
+ end
47
57
  end
58
+ super
48
59
  end
49
60
 
50
- private
51
- def create_time_zone_conversion_attribute?(name, column)
61
+ def create_time_zone_conversion_attribute?(name, cast_type)
52
62
  time_zone_aware_attributes &&
53
63
  !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
- (:datetime == column.type || :timestamp == column.type)
55
- end
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
64
+ (:datetime == cast_type.type)
67
65
  end
68
66
  end
69
67
  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 {
@@ -52,14 +50,14 @@ module ActiveRecord
52
50
  end
53
51
 
54
52
  # Updates the attribute identified by <tt>attr_name</tt> with the
55
- # specified +value+. Empty strings for fixnum and float columns are
53
+ # specified +value+. Empty strings for Integer and Float columns are
56
54
  # turned into +nil+.
57
55
  def write_attribute(attr_name, value)
58
- write_attribute_with_type_cast(attr_name, value, :type_cast_attribute_for_write)
56
+ write_attribute_with_type_cast(attr_name, value, true)
59
57
  end
60
58
 
61
59
  def raw_write_attribute(attr_name, value)
62
- write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write)
60
+ write_attribute_with_type_cast(attr_name, value, false)
63
61
  end
64
62
 
65
63
  private
@@ -68,30 +66,17 @@ module ActiveRecord
68
66
  write_attribute(attribute_name, value)
69
67
  end
70
68
 
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)
69
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
79
70
  attr_name = attr_name.to_s
80
71
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
81
- @attributes_cache.delete(attr_name)
82
- column = column_for_attribute(attr_name)
83
72
 
84
- # If we're dealing with a binary column, write the data to the cache
85
- # so we don't attempt to typecast multiple times.
86
- if column && column.binary?
87
- @attributes_cache[attr_name] = value
88
- end
89
-
90
- if column || @attributes.has_key?(attr_name)
91
- @attributes[attr_name] = send(type_cast_method, column, value)
73
+ if should_type_cast
74
+ @attributes.write_from_user(attr_name, value)
92
75
  else
93
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
76
+ @attributes.write_cast_value(attr_name, value)
94
77
  end
78
+
79
+ value
95
80
  end
96
81
  end
97
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 {
@@ -48,7 +51,11 @@ 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
 
54
61
  class GeneratedAttributeMethods < Module; end # :nodoc:
@@ -71,7 +78,7 @@ module ActiveRecord
71
78
  # accessors, mutators and query methods.
72
79
  def define_attribute_methods # :nodoc:
73
80
  return false if @attribute_methods_generated
74
- # Use a mutex; we don't want two thread simultaneously trying to define
81
+ # Use a mutex; we don't want two threads simultaneously trying to define
75
82
  # attribute methods.
76
83
  generated_attribute_methods.synchronize do
77
84
  return false if @attribute_methods_generated
@@ -155,16 +162,6 @@ module ActiveRecord
155
162
  end
156
163
  end
157
164
 
158
- def find_generated_attribute_method(method_name) # :nodoc:
159
- klass = self
160
- until klass == Base
161
- gen_methods = klass.generated_attribute_methods
162
- return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
163
- klass = klass.superclass
164
- end
165
- nil
166
- end
167
-
168
165
  # Returns +true+ if +attribute+ is an attribute method and table exists,
169
166
  # +false+ otherwise.
170
167
  #
@@ -193,24 +190,29 @@ module ActiveRecord
193
190
  []
194
191
  end
195
192
  end
196
- end
197
193
 
198
- # If we haven't generated any methods yet, generate them, then
199
- # see if we've created the method we're looking for.
200
- def method_missing(method, *args, &block) # :nodoc:
201
- self.class.define_attribute_methods
202
- if respond_to_without_attributes?(method)
203
- # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
204
- # call in a overwritten attribute method
205
- if attribute_method = self.class.find_generated_attribute_method(method)
206
- # this is probably horribly slow, but should only happen at most once for a given AR class
207
- attribute_method.bind(self).call(*args, &block)
208
- else
209
- return super unless respond_to_missing?(method, true)
210
- 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
211
214
  end
212
- else
213
- super
215
+ column
214
216
  end
215
217
  end
216
218
 
@@ -231,18 +233,14 @@ module ActiveRecord
231
233
  # person.respond_to('age?') # => true
232
234
  # person.respond_to(:nothing) # => false
233
235
  def respond_to?(name, include_private = false)
236
+ return false unless super
234
237
  name = name.to_s
235
- self.class.define_attribute_methods
236
- result = super
237
-
238
- # If the result is false the answer is false.
239
- return false unless result
240
238
 
241
239
  # If the result is true then check for the select case.
242
240
  # For queries selecting a subset of columns, return false for unselected columns.
243
241
  # We check defined?(@attributes) not to issue warnings if called on objects that
244
242
  # have been allocated but not yet initialized.
245
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
243
+ if defined?(@attributes) && self.class.column_names.include?(name)
246
244
  return has_attribute?(name)
247
245
  end
248
246
 
@@ -259,7 +257,7 @@ module ActiveRecord
259
257
  # person.has_attribute?('age') # => true
260
258
  # person.has_attribute?(:nothing) # => false
261
259
  def has_attribute?(attr_name)
262
- @attributes.has_key?(attr_name.to_s)
260
+ @attributes.key?(attr_name.to_s)
263
261
  end
264
262
 
265
263
  # Returns an array of names for the attributes available on this object.
@@ -283,22 +281,14 @@ module ActiveRecord
283
281
  # person.attributes
284
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}
285
283
  def attributes
286
- attribute_names.each_with_object({}) { |name, attrs|
287
- attrs[name] = read_attribute(name)
288
- }
289
- end
290
-
291
- # Placeholder so it can be overriden when needed by serialization
292
- def attributes_for_coder # :nodoc:
293
- attributes
284
+ @attributes.to_hash
294
285
  end
295
286
 
296
287
  # Returns an <tt>#inspect</tt>-like string for the value of the
297
- # attribute +attr_name+. String attributes are truncated upto 50
288
+ # attribute +attr_name+. String attributes are truncated up to 50
298
289
  # 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.
290
+ # <tt>:db</tt> format. Other attributes return the value of
291
+ # <tt>#inspect</tt> without modification.
302
292
  #
303
293
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
304
294
  #
@@ -309,7 +299,7 @@ module ActiveRecord
309
299
  # # => "\"2012-10-22 00:15:07\""
310
300
  #
311
301
  # person.attribute_for_inspect(:tag_ids)
312
- # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
302
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
313
303
  def attribute_for_inspect(attr_name)
314
304
  value = read_attribute(attr_name)
315
305
 
@@ -317,9 +307,6 @@ module ActiveRecord
317
307
  "#{value[0, 50]}...".inspect
318
308
  elsif value.is_a?(Date) || value.is_a?(Time)
319
309
  %("#{value.to_s(:db)}")
320
- elsif value.is_a?(Array) && value.size > 10
321
- inspected = value.first(10).inspect
322
- %(#{inspected[0...-1]}, ...])
323
310
  else
324
311
  value.inspect
325
312
  end
@@ -333,39 +320,24 @@ module ActiveRecord
333
320
  # class Task < ActiveRecord::Base
334
321
  # end
335
322
  #
336
- # person = Task.new(title: '', is_done: false)
337
- # person.attribute_present?(:title) # => false
338
- # person.attribute_present?(:is_done) # => true
339
- # person.name = 'Francesco'
340
- # person.is_done = true
341
- # person.attribute_present?(:title) # => true
342
- # person.attribute_present?(:is_done) # => true
323
+ # task = Task.new(title: '', is_done: false)
324
+ # task.attribute_present?(:title) # => false
325
+ # task.attribute_present?(:is_done) # => true
326
+ # task.title = 'Buy milk'
327
+ # task.is_done = true
328
+ # task.attribute_present?(:title) # => true
329
+ # task.attribute_present?(:is_done) # => true
343
330
  def attribute_present?(attribute)
344
- value = read_attribute(attribute)
331
+ value = _read_attribute(attribute)
345
332
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
346
333
  end
347
334
 
348
- # Returns the column object for the named attribute. Returns +nil+ if the
349
- # named attribute not exists.
350
- #
351
- # class Person < ActiveRecord::Base
352
- # end
353
- #
354
- # person = Person.new
355
- # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
356
- # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
357
- #
358
- # person.column_for_attribute(:nothing)
359
- # # => nil
360
- def column_for_attribute(name)
361
- # FIXME: should this return a null object for columns that don't exist?
362
- self.class.columns_hash[name.to_s]
363
- end
364
-
365
335
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
366
336
  # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
367
337
  # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
368
338
  #
339
+ # Note: +:id+ is always present.
340
+ #
369
341
  # Alias for the <tt>read_attribute</tt> method.
370
342
  #
371
343
  # class Person < ActiveRecord::Base
@@ -392,20 +364,13 @@ module ActiveRecord
392
364
  # person = Person.new
393
365
  # person[:age] = '22'
394
366
  # person[:age] # => 22
395
- # person[:age] # => Fixnum
367
+ # person[:age].class # => Integer
396
368
  def []=(attr_name, value)
397
369
  write_attribute(attr_name, value)
398
370
  end
399
371
 
400
372
  protected
401
373
 
402
- def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
403
- attribute_names.each do |name|
404
- attributes[name] = clone_attribute_value(reader_method, name)
405
- end
406
- attributes
407
- end
408
-
409
374
  def clone_attribute_value(reader_method, attribute_name) # :nodoc:
410
375
  value = send(reader_method, attribute_name)
411
376
  value.duplicable? ? value.clone : value
@@ -423,7 +388,7 @@ module ActiveRecord
423
388
 
424
389
  def attribute_method?(attr_name) # :nodoc:
425
390
  # We check defined? because Syck calls respond_to? before actually calling initialize.
426
- defined?(@attributes) && @attributes.include?(attr_name)
391
+ defined?(@attributes) && @attributes.key?(attr_name)
427
392
  end
428
393
 
429
394
  private
@@ -442,16 +407,16 @@ module ActiveRecord
442
407
 
443
408
  # Filters the primary keys and readonly attributes from the attribute names.
444
409
  def attributes_for_update(attribute_names)
445
- attribute_names.select do |name|
446
- column_for_attribute(name) && !readonly_attribute?(name)
410
+ attribute_names.reject do |name|
411
+ readonly_attribute?(name)
447
412
  end
448
413
  end
449
414
 
450
415
  # Filters out the primary keys, from the attribute names, when the primary
451
416
  # key is to be generated (e.g. the id attribute has no value).
452
417
  def attributes_for_create(attribute_names)
453
- attribute_names.select do |name|
454
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
418
+ attribute_names.reject do |name|
419
+ pk_attribute?(name) && id.nil?
455
420
  end
456
421
  end
457
422
 
@@ -460,14 +425,11 @@ module ActiveRecord
460
425
  end
461
426
 
462
427
  def pk_attribute?(name)
463
- column_for_attribute(name).primary
428
+ name == self.class.primary_key
464
429
  end
465
430
 
466
431
  def typecasted_attribute_value(name)
467
- # FIXME: we need @attributes to be used consistently.
468
- # If the values stored in @attributes were already typecasted, this code
469
- # could be simplified
470
- read_attribute(name)
432
+ _read_attribute(name)
471
433
  end
472
434
  end
473
435
  end