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
@@ -11,6 +11,15 @@ module ActiveRecord
11
11
  # If the passed hash responds to <tt>permitted?</tt> method and the return value
12
12
  # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
13
  # exception is raised.
14
+ #
15
+ # cat = Cat.new(name: "Gorby", status: "yawning")
16
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
17
+ # cat.assign_attributes(status: "sleeping")
18
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
19
+ #
20
+ # New attributes will be persisted in the database when the object is saved.
21
+ #
22
+ # Aliased to <tt>attributes=</tt>.
14
23
  def assign_attributes(new_attributes)
15
24
  if !new_attributes.respond_to?(:stringify_keys)
16
25
  raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
@@ -106,7 +115,7 @@ module ActiveRecord
106
115
  end
107
116
 
108
117
  class MultiparameterAttribute #:nodoc:
109
- attr_reader :object, :name, :values, :column
118
+ attr_reader :object, :name, :values, :cast_type
110
119
 
111
120
  def initialize(object, name, values)
112
121
  @object = object
@@ -117,22 +126,22 @@ module ActiveRecord
117
126
  def read_value
118
127
  return if values.values.compact.empty?
119
128
 
120
- @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
121
- klass = column.klass
129
+ @cast_type = object.type_for_attribute(name)
130
+ klass = cast_type.klass
122
131
 
123
132
  if klass == Time
124
133
  read_time
125
134
  elsif klass == Date
126
135
  read_date
127
136
  else
128
- read_other(klass)
137
+ read_other
129
138
  end
130
139
  end
131
140
 
132
141
  private
133
142
 
134
143
  def instantiate_time_object(set_values)
135
- if object.class.send(:create_time_zone_conversion_attribute?, name, column)
144
+ if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
136
145
  Time.zone.local(*set_values)
137
146
  else
138
147
  Time.send(object.class.default_timezone, *set_values)
@@ -140,9 +149,9 @@ module ActiveRecord
140
149
  end
141
150
 
142
151
  def read_time
143
- # If column is a :time (and not :date or :timestamp) there is no need to validate if
152
+ # If column is a :time (and not :date or :datetime) there is no need to validate if
144
153
  # there are year/month/day fields
145
- if column.type == :time
154
+ if cast_type.type == :time
146
155
  # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
147
156
  { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
148
157
  values[key] ||= value
@@ -172,13 +181,12 @@ module ActiveRecord
172
181
  end
173
182
  end
174
183
 
175
- def read_other(klass)
184
+ def read_other
176
185
  max_position = extract_max_param
177
186
  positions = (1..max_position)
178
187
  validate_required_parameters!(positions)
179
188
 
180
- set_values = values.values_at(*positions)
181
- klass.new(*set_values)
189
+ values.slice(*positions)
182
190
  end
183
191
 
184
192
  # Checks whether some blank date parameter exists. Note that this is different
@@ -0,0 +1,66 @@
1
+ module ActiveRecord
2
+ module AttributeDecorators # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
7
+ self.attribute_type_decorations = TypeDecorator.new
8
+ end
9
+
10
+ module ClassMethods # :nodoc:
11
+ def decorate_attribute_type(column_name, decorator_name, &block)
12
+ matcher = ->(name, _) { name == column_name.to_s }
13
+ key = "_#{column_name}_#{decorator_name}"
14
+ decorate_matching_attribute_types(matcher, key, &block)
15
+ end
16
+
17
+ def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
+ clear_caches_calculated_from_columns
19
+ decorator_name = decorator_name.to_s
20
+
21
+ # Create new hashes so we don't modify parent classes
22
+ self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
23
+ end
24
+
25
+ private
26
+
27
+ def add_user_provided_columns(*)
28
+ super.map do |column|
29
+ decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
30
+ column.with_type(decorated_type)
31
+ end
32
+ end
33
+ end
34
+
35
+ class TypeDecorator # :nodoc:
36
+ delegate :clear, to: :@decorations
37
+
38
+ def initialize(decorations = {})
39
+ @decorations = decorations
40
+ end
41
+
42
+ def merge(*args)
43
+ TypeDecorator.new(@decorations.merge(*args))
44
+ end
45
+
46
+ def apply(name, type)
47
+ decorations = decorators_for(name, type)
48
+ decorations.inject(type) do |new_type, block|
49
+ block.call(new_type)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def decorators_for(name, type)
56
+ matching(name, type).map(&:last)
57
+ end
58
+
59
+ def matching(name, type)
60
+ @decorations.values.select do |(matcher, _)|
61
+ matcher.call(name, type)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -43,7 +43,7 @@ module ActiveRecord
43
43
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
44
44
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
45
45
  def read_attribute_before_type_cast(attr_name)
46
- @attributes[attr_name.to_s]
46
+ @attributes[attr_name.to_s].value_before_type_cast
47
47
  end
48
48
 
49
49
  # Returns a hash of attributes before typecasting and deserialization.
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  # task.attributes_before_type_cast
58
58
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
59
59
  def attributes_before_type_cast
60
- @attributes
60
+ @attributes.values_before_type_cast
61
61
  end
62
62
 
63
63
  private
@@ -34,28 +34,52 @@ module ActiveRecord
34
34
  # <tt>reload</tt> the record and clears changed attributes.
35
35
  def reload(*)
36
36
  super.tap do
37
- reset_changes
37
+ clear_changes_information
38
38
  end
39
39
  end
40
40
 
41
- def initialize_dup(other) # :nodoc:
42
- super
43
- init_changed_attributes
44
- end
41
+ def initialize_dup(other) # :nodoc:
42
+ super
43
+ calculate_changes_from_defaults
44
+ end
45
45
 
46
- private
47
- def initialize_internals_callback
46
+ def changes_applied
48
47
  super
49
- init_changed_attributes
48
+ store_original_raw_attributes
49
+ end
50
+
51
+ def clear_changes_information
52
+ super
53
+ original_raw_attributes.clear
54
+ end
55
+
56
+ def changed_attributes
57
+ # This should only be set by methods which will call changed_attributes
58
+ # multiple times when it is known that the computed value cannot change.
59
+ if defined?(@cached_changed_attributes)
60
+ @cached_changed_attributes
61
+ else
62
+ super.reverse_merge(attributes_changed_in_place).freeze
63
+ end
50
64
  end
51
65
 
52
- def init_changed_attributes
66
+ def changes
67
+ cache_changed_attributes do
68
+ super
69
+ end
70
+ end
71
+
72
+ def attribute_changed_in_place?(attr_name)
73
+ old_value = original_raw_attribute(attr_name)
74
+ @attributes[attr_name].changed_in_place_from?(old_value)
75
+ end
76
+
77
+ private
78
+
79
+ def calculate_changes_from_defaults
53
80
  @changed_attributes = nil
54
- # Intentionally avoid using #column_defaults since overridden defaults (as is done in
55
- # optimistic locking) won't get written unless they get marked as changed
56
- self.class.columns.each do |c|
57
- attr, orig_value = c.name, c.default
58
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
81
+ self.class.column_defaults.each do |attr, orig_value|
82
+ set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
59
83
  end
60
84
  end
61
85
 
@@ -63,27 +87,43 @@ module ActiveRecord
63
87
  def write_attribute(attr, value)
64
88
  attr = attr.to_s
65
89
 
66
- save_changed_attribute(attr, value)
90
+ old_value = old_attribute_value(attr)
67
91
 
68
- super(attr, value)
92
+ result = super
93
+ store_original_raw_attribute(attr)
94
+ save_changed_attribute(attr, old_value)
95
+ result
69
96
  end
70
97
 
71
- def save_changed_attribute(attr, value)
72
- # The attribute already has an unsaved change.
98
+ def raw_write_attribute(attr, value)
99
+ attr = attr.to_s
100
+
101
+ result = super
102
+ original_raw_attributes[attr] = value
103
+ result
104
+ end
105
+
106
+ def save_changed_attribute(attr, old_value)
73
107
  if attribute_changed?(attr)
74
- old = changed_attributes[attr]
75
- changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
108
+ clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
76
109
  else
77
- old = clone_attribute_value(:read_attribute, attr)
78
- changed_attributes[attr] = old if _field_changed?(attr, old, value)
110
+ set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
79
111
  end
80
112
  end
81
113
 
82
- def update_record(*)
114
+ def old_attribute_value(attr)
115
+ if attribute_changed?(attr)
116
+ changed_attributes[attr]
117
+ else
118
+ clone_attribute_value(:_read_attribute, attr)
119
+ end
120
+ end
121
+
122
+ def _update_record(*)
83
123
  partial_writes? ? super(keys_for_partial_write) : super
84
124
  end
85
125
 
86
- def create_record(*)
126
+ def _create_record(*)
87
127
  partial_writes? ? super(keys_for_partial_write) : super
88
128
  end
89
129
 
@@ -93,34 +133,48 @@ module ActiveRecord
93
133
  changed
94
134
  end
95
135
 
96
- def _field_changed?(attr, old, value)
97
- if column = column_for_attribute(attr)
98
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
99
- changes_from_zero_to_string?(old, value))
100
- value = nil
101
- else
102
- value = column.type_cast(value)
103
- end
136
+ def _field_changed?(attr, old_value)
137
+ @attributes[attr].changed_from?(old_value)
138
+ end
139
+
140
+ def attributes_changed_in_place
141
+ changed_in_place.each_with_object({}) do |attr_name, h|
142
+ orig = @attributes[attr_name].original_value
143
+ h[attr_name] = orig
104
144
  end
145
+ end
146
+
147
+ def changed_in_place
148
+ self.class.attribute_names.select do |attr_name|
149
+ attribute_changed_in_place?(attr_name)
150
+ end
151
+ end
105
152
 
106
- old != value
153
+ def original_raw_attribute(attr_name)
154
+ original_raw_attributes.fetch(attr_name) do
155
+ read_attribute_before_type_cast(attr_name)
156
+ end
157
+ end
158
+
159
+ def original_raw_attributes
160
+ @original_raw_attributes ||= {}
107
161
  end
108
162
 
109
- def changes_from_nil_to_empty_string?(column, old, value)
110
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
111
- # Hence we don't record it as a change if the value changes from nil to ''.
112
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
113
- # be typecast back to 0 (''.to_i => 0)
114
- column.null && (old.nil? || old == 0) && value.blank?
163
+ def store_original_raw_attribute(attr_name)
164
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
115
165
  end
116
166
 
117
- def changes_from_zero_to_string?(old, value)
118
- # For columns with old 0 and value non-empty string
119
- old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
167
+ def store_original_raw_attributes
168
+ attribute_names.each do |attr|
169
+ store_original_raw_attribute(attr)
170
+ end
120
171
  end
121
172
 
122
- def non_zero?(value)
123
- value !~ /\A0+(\.0+)?\z/
173
+ def cache_changed_attributes
174
+ @cached_changed_attributes = changed_attributes
175
+ yield
176
+ ensure
177
+ remove_instance_variable(:@cached_changed_attributes)
124
178
  end
125
179
  end
126
180
  end
@@ -15,8 +15,10 @@ module ActiveRecord
15
15
 
16
16
  # Returns the primary key value.
17
17
  def id
18
- sync_with_transaction_state
19
- read_attribute(self.class.primary_key)
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ _read_attribute(pk)
21
+ end
20
22
  end
21
23
 
22
24
  # Sets the primary key value.
@@ -37,6 +39,12 @@ module ActiveRecord
37
39
  read_attribute_before_type_cast(self.class.primary_key)
38
40
  end
39
41
 
42
+ # Returns the primary key previous value.
43
+ def id_was
44
+ sync_with_transaction_state
45
+ attribute_was(self.class.primary_key)
46
+ end
47
+
40
48
  protected
41
49
 
42
50
  def attribute_method?(attr_name)
@@ -52,7 +60,7 @@ module ActiveRecord
52
60
  end
53
61
  end
54
62
 
55
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
63
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
56
64
 
57
65
  def dangerous_attribute_method?(method_name)
58
66
  super && !ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -81,12 +89,9 @@ module ActiveRecord
81
89
  end
82
90
 
83
91
  def get_primary_key(base_name) #:nodoc:
84
- return 'id' if base_name.blank?
85
-
86
- case primary_key_prefix_type
87
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
88
93
  base_name.foreign_key(false)
89
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
90
95
  base_name.foreign_key
91
96
  else
92
97
  if ActiveRecord::Base != self && table_exists?
@@ -115,6 +120,7 @@ module ActiveRecord
115
120
  def primary_key=(value)
116
121
  @primary_key = value && value.to_s
117
122
  @quoted_primary_key = nil
123
+ @attributes_builder = nil
118
124
  end
119
125
  end
120
126
  end
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def query_attribute(attr_name)
11
- value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
11
+ value = self[attr_name]
12
12
 
13
13
  case value
14
14
  when true then true
@@ -22,12 +22,12 @@ module ActiveRecord
22
22
  # the attribute name. Using a constant means that we do not have
23
23
  # to allocate an object on each call to the attribute method.
24
24
  # Making it frozen means that it doesn't get duped when used to
25
- # key the @attributes_cache in read_attribute.
25
+ # key the @attributes in read_attribute.
26
26
  def method_body(method_name, const_name)
27
27
  <<-EOMETHOD
28
28
  def #{method_name}
29
29
  name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
30
- read_attribute(name) { |n| missing_attribute(n, caller) }
30
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
31
31
  end
32
32
  EOMETHOD
33
33
  end
@@ -35,35 +35,20 @@ module ActiveRecord
35
35
 
36
36
  extend ActiveSupport::Concern
37
37
 
38
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
39
-
40
- included do
41
- class_attribute :attribute_types_cached_by_default, instance_writer: false
42
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
43
- end
44
-
45
38
  module ClassMethods
46
- # +cache_attributes+ allows you to declare which converted attribute
47
- # values should be cached. Usually caching only pays off for attributes
48
- # with expensive conversion methods, like time related columns (e.g.
49
- # +created_at+, +updated_at+).
50
- def cache_attributes(*attribute_names)
51
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
39
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
40
+ define_method method_name do |*|
41
+ cached_attributes_deprecation_warning(method_name)
42
+ true
43
+ end
52
44
  end
53
45
 
54
- # Returns the attributes which are cached. By default time related columns
55
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
56
- def cached_attributes
57
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
58
- end
46
+ protected
59
47
 
60
- # Returns +true+ if the provided attribute is being cached.
61
- def cache_attribute?(attr_name)
62
- cached_attributes.include?(attr_name)
48
+ def cached_attributes_deprecation_warning(method_name)
49
+ ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
63
50
  end
64
51
 
65
- protected
66
-
67
52
  if Module.methods_transplantable?
68
53
  def define_method_attribute(name)
69
54
  method = ReaderMethodCache[name]
@@ -79,7 +64,7 @@ module ActiveRecord
79
64
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
80
65
  def #{temp_method}
81
66
  name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
82
- read_attribute(name) { |n| missing_attribute(n, caller) }
67
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
83
68
  end
84
69
  STR
85
70
 
@@ -89,51 +74,29 @@ module ActiveRecord
89
74
  end
90
75
  end
91
76
  end
92
-
93
- private
94
-
95
- def cacheable_column?(column)
96
- if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
97
- ! serialized_attributes.include? column.name
98
- else
99
- attribute_types_cached_by_default.include?(column.type)
100
- end
101
- end
102
77
  end
103
78
 
79
+ ID = 'id'.freeze
80
+
104
81
  # Returns the value of the attribute identified by <tt>attr_name</tt> after
105
82
  # it has been typecast (for example, "2004-12-12" in a date column is cast
106
83
  # to a date object, like Date.new(2004, 12, 12)).
107
- def read_attribute(attr_name)
108
- # If it's cached, just return it
109
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
84
+ def read_attribute(attr_name, &block)
110
85
  name = attr_name.to_s
111
- @attributes_cache[name] || @attributes_cache.fetch(name) {
112
- column = @column_types_override[name] if @column_types_override
113
- column ||= @column_types[name]
114
-
115
- return @attributes.fetch(name) {
116
- if name == 'id' && self.class.primary_key != name
117
- read_attribute(self.class.primary_key)
118
- end
119
- } unless column
120
-
121
- value = @attributes.fetch(name) {
122
- return block_given? ? yield(name) : nil
123
- }
86
+ name = self.class.primary_key if name == ID
87
+ _read_attribute(name, &block)
88
+ end
124
89
 
125
- if self.class.cache_attribute?(name)
126
- @attributes_cache[name] = column.type_cast(value)
127
- else
128
- column.type_cast value
129
- end
130
- }
90
+ # This method exists to avoid the expensive primary_key check internally, without
91
+ # breaking compatibility with the read_attribute API
92
+ def _read_attribute(attr_name) # :nodoc:
93
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
131
94
  end
132
95
 
133
96
  private
134
97
 
135
98
  def attribute(attribute_name)
136
- read_attribute(attribute_name)
99
+ _read_attribute(attribute_name)
137
100
  end
138
101
  end
139
102
  end