activerecord 5.0.7.2 → 5.1.0.beta1

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 (216) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +389 -2252
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record.rb +20 -20
  8. data/lib/active_record/aggregations.rb +244 -244
  9. data/lib/active_record/association_relation.rb +5 -5
  10. data/lib/active_record/associations.rb +1579 -1569
  11. data/lib/active_record/associations/alias_tracker.rb +1 -1
  12. data/lib/active_record/associations/association.rb +23 -15
  13. data/lib/active_record/associations/association_scope.rb +83 -81
  14. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  15. data/lib/active_record/associations/builder/belongs_to.rb +16 -14
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  18. data/lib/active_record/associations/collection_association.rb +74 -241
  19. data/lib/active_record/associations/collection_proxy.rb +144 -70
  20. data/lib/active_record/associations/has_many_association.rb +15 -19
  21. data/lib/active_record/associations/has_many_through_association.rb +12 -5
  22. data/lib/active_record/associations/has_one_association.rb +22 -28
  23. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  24. data/lib/active_record/associations/join_dependency.rb +117 -115
  25. data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
  26. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  28. data/lib/active_record/associations/preloader.rb +94 -94
  29. data/lib/active_record/associations/preloader/association.rb +87 -64
  30. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  31. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  32. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  33. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +34 -41
  35. data/lib/active_record/associations/singular_association.rb +8 -25
  36. data/lib/active_record/associations/through_association.rb +3 -6
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  39. data/lib/active_record/attribute_assignment.rb +61 -61
  40. data/lib/active_record/attribute_decorators.rb +35 -13
  41. data/lib/active_record/attribute_methods.rb +56 -65
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  43. data/lib/active_record/attribute_methods/dirty.rb +216 -34
  44. data/lib/active_record/attribute_methods/primary_key.rb +78 -73
  45. data/lib/active_record/attribute_methods/read.rb +39 -35
  46. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  48. data/lib/active_record/attribute_methods/write.rb +36 -30
  49. data/lib/active_record/attribute_mutation_tracker.rb +53 -10
  50. data/lib/active_record/attribute_set.rb +9 -6
  51. data/lib/active_record/attribute_set/builder.rb +41 -49
  52. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  53. data/lib/active_record/attributes.rb +21 -21
  54. data/lib/active_record/autosave_association.rb +13 -13
  55. data/lib/active_record/base.rb +24 -22
  56. data/lib/active_record/callbacks.rb +52 -14
  57. data/lib/active_record/coders/yaml_column.rb +9 -11
  58. data/lib/active_record/collection_cache_key.rb +6 -17
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
  71. data/lib/active_record/connection_adapters/column.rb +27 -5
  72. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  73. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  94. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  97. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
  98. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
  116. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  117. data/lib/active_record/connection_handling.rb +14 -26
  118. data/lib/active_record/core.rb +110 -93
  119. data/lib/active_record/counter_cache.rb +62 -13
  120. data/lib/active_record/define_callbacks.rb +20 -0
  121. data/lib/active_record/dynamic_matchers.rb +80 -79
  122. data/lib/active_record/enum.rb +8 -6
  123. data/lib/active_record/errors.rb +58 -15
  124. data/lib/active_record/explain.rb +1 -2
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +7 -4
  127. data/lib/active_record/fixture_set/file.rb +11 -8
  128. data/lib/active_record/fixtures.rb +66 -53
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +93 -79
  131. data/lib/active_record/integration.rb +7 -7
  132. data/lib/active_record/internal_metadata.rb +3 -16
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +64 -56
  135. data/lib/active_record/locking/pessimistic.rb +10 -1
  136. data/lib/active_record/log_subscriber.rb +29 -29
  137. data/lib/active_record/migration.rb +155 -172
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +76 -37
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/model_schema.rb +85 -119
  142. data/lib/active_record/nested_attributes.rb +200 -199
  143. data/lib/active_record/null_relation.rb +10 -33
  144. data/lib/active_record/persistence.rb +45 -38
  145. data/lib/active_record/query_cache.rb +4 -8
  146. data/lib/active_record/querying.rb +2 -3
  147. data/lib/active_record/railtie.rb +16 -17
  148. data/lib/active_record/railties/controller_runtime.rb +6 -2
  149. data/lib/active_record/railties/databases.rake +125 -140
  150. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  151. data/lib/active_record/readonly_attributes.rb +2 -2
  152. data/lib/active_record/reflection.rb +79 -96
  153. data/lib/active_record/relation.rb +72 -115
  154. data/lib/active_record/relation/batches.rb +87 -58
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  156. data/lib/active_record/relation/calculations.rb +154 -160
  157. data/lib/active_record/relation/delegation.rb +30 -29
  158. data/lib/active_record/relation/finder_methods.rb +195 -226
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder.rb +92 -89
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  165. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  166. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +247 -295
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +79 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/result.rb +29 -31
  174. data/lib/active_record/runtime_registry.rb +3 -3
  175. data/lib/active_record/sanitization.rb +182 -197
  176. data/lib/active_record/schema.rb +3 -3
  177. data/lib/active_record/schema_dumper.rb +14 -37
  178. data/lib/active_record/schema_migration.rb +3 -3
  179. data/lib/active_record/scoping.rb +9 -10
  180. data/lib/active_record/scoping/default.rb +87 -91
  181. data/lib/active_record/scoping/named.rb +16 -28
  182. data/lib/active_record/secure_token.rb +2 -2
  183. data/lib/active_record/statement_cache.rb +13 -15
  184. data/lib/active_record/store.rb +31 -32
  185. data/lib/active_record/suppressor.rb +2 -1
  186. data/lib/active_record/table_metadata.rb +9 -5
  187. data/lib/active_record/tasks/database_tasks.rb +72 -65
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  191. data/lib/active_record/timestamp.rb +39 -25
  192. data/lib/active_record/touch_later.rb +1 -2
  193. data/lib/active_record/transactions.rb +98 -110
  194. data/lib/active_record/type.rb +17 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +9 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/serialized.rb +8 -8
  199. data/lib/active_record/type/text.rb +9 -0
  200. data/lib/active_record/type/time.rb +0 -1
  201. data/lib/active_record/type/type_map.rb +11 -15
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type_caster.rb +2 -2
  204. data/lib/active_record/type_caster/connection.rb +8 -6
  205. data/lib/active_record/type_caster/map.rb +3 -1
  206. data/lib/active_record/validations.rb +4 -4
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/presence.rb +2 -2
  209. data/lib/active_record/validations/uniqueness.rb +8 -39
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/rails/generators/active_record.rb +4 -4
  212. data/lib/rails/generators/active_record/migration.rb +2 -2
  213. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  215. metadata +22 -13
  216. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,4 +1,4 @@
1
- require 'active_record/attribute'
1
+ require "active_record/attribute"
2
2
 
3
3
  module ActiveRecord
4
4
  class Attribute # :nodoc:
@@ -20,9 +20,11 @@ module ActiveRecord
20
20
  self.class.new(name, user_provided_value, type, original_attribute)
21
21
  end
22
22
 
23
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
24
+ # Workaround for Ruby 2.2 "private attribute?" warning.
23
25
  protected
24
26
 
25
- attr_reader :user_provided_value
27
+ attr_reader :user_provided_value
26
28
  end
27
29
  end
28
30
  end
@@ -1,4 +1,4 @@
1
- require 'active_model/forbidden_attributes_protection'
1
+ require "active_model/forbidden_attributes_protection"
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeAssignment
@@ -12,80 +12,80 @@ module ActiveRecord
12
12
 
13
13
  private
14
14
 
15
- def _assign_attributes(attributes) # :nodoc:
16
- multi_parameter_attributes = {}
17
- nested_parameter_attributes = {}
15
+ def _assign_attributes(attributes)
16
+ multi_parameter_attributes = {}
17
+ nested_parameter_attributes = {}
18
18
 
19
- attributes.each do |k, v|
20
- if k.include?("(")
21
- multi_parameter_attributes[k] = attributes.delete(k)
22
- elsif v.is_a?(Hash)
23
- nested_parameter_attributes[k] = attributes.delete(k)
19
+ attributes.each do |k, v|
20
+ if k.include?("(")
21
+ multi_parameter_attributes[k] = attributes.delete(k)
22
+ elsif v.is_a?(Hash)
23
+ nested_parameter_attributes[k] = attributes.delete(k)
24
+ end
24
25
  end
25
- end
26
- super(attributes)
26
+ super(attributes)
27
27
 
28
- assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
29
- assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
30
- end
28
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
29
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
30
+ end
31
31
 
32
- # Assign any deferred nested attributes after the base attributes have been set.
33
- def assign_nested_parameter_attributes(pairs)
34
- pairs.each { |k, v| _assign_attribute(k, v) }
35
- end
32
+ # Assign any deferred nested attributes after the base attributes have been set.
33
+ def assign_nested_parameter_attributes(pairs)
34
+ pairs.each { |k, v| _assign_attribute(k, v) }
35
+ end
36
36
 
37
- # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
38
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
39
- # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
40
- # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
41
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
42
- # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
43
- def assign_multiparameter_attributes(pairs)
44
- execute_callstack_for_multiparameter_attributes(
45
- extract_callstack_for_multiparameter_attributes(pairs)
46
- )
47
- end
37
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
38
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
39
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
40
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
41
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
42
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
43
+ def assign_multiparameter_attributes(pairs)
44
+ execute_callstack_for_multiparameter_attributes(
45
+ extract_callstack_for_multiparameter_attributes(pairs)
46
+ )
47
+ end
48
48
 
49
- def execute_callstack_for_multiparameter_attributes(callstack)
50
- errors = []
51
- callstack.each do |name, values_with_empty_parameters|
52
- begin
53
- if values_with_empty_parameters.each_value.all?(&:nil?)
54
- values = nil
55
- else
56
- values = values_with_empty_parameters
49
+ def execute_callstack_for_multiparameter_attributes(callstack)
50
+ errors = []
51
+ callstack.each do |name, values_with_empty_parameters|
52
+ begin
53
+ if values_with_empty_parameters.each_value.all?(&:nil?)
54
+ values = nil
55
+ else
56
+ values = values_with_empty_parameters
57
+ end
58
+ send("#{name}=", values)
59
+ rescue => ex
60
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
57
61
  end
58
- send("#{name}=", values)
59
- rescue => ex
60
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
62
+ end
63
+ unless errors.empty?
64
+ error_descriptions = errors.map(&:message).join(",")
65
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
61
66
  end
62
67
  end
63
- unless errors.empty?
64
- error_descriptions = errors.map(&:message).join(",")
65
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
66
- end
67
- end
68
68
 
69
- def extract_callstack_for_multiparameter_attributes(pairs)
70
- attributes = {}
69
+ def extract_callstack_for_multiparameter_attributes(pairs)
70
+ attributes = {}
71
71
 
72
- pairs.each do |(multiparameter_name, value)|
73
- attribute_name = multiparameter_name.split("(").first
74
- attributes[attribute_name] ||= {}
72
+ pairs.each do |(multiparameter_name, value)|
73
+ attribute_name = multiparameter_name.split("(").first
74
+ attributes[attribute_name] ||= {}
75
75
 
76
- parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
77
- attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
78
- end
76
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
77
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
78
+ end
79
79
 
80
- attributes
81
- end
80
+ attributes
81
+ end
82
82
 
83
- def type_cast_attribute_value(multiparameter_name, value)
84
- multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
85
- end
83
+ def type_cast_attribute_value(multiparameter_name, value)
84
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
85
+ end
86
86
 
87
- def find_parameter_position(multiparameter_name)
88
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
89
- end
87
+ def find_parameter_position(multiparameter_name)
88
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
89
+ end
90
90
  end
91
91
  end
@@ -8,12 +8,34 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  module ClassMethods # :nodoc:
11
+ # This method is an internal API used to create class macros such as
12
+ # +serialize+, and features like time zone aware attributes.
13
+ #
14
+ # Used to wrap the type of an attribute in a new type.
15
+ # When the schema for a model is loaded, attributes with the same name as
16
+ # +column_name+ will have their type yielded to the given block. The
17
+ # return value of that block will be used instead.
18
+ #
19
+ # Subsequent calls where +column_name+ and +decorator_name+ are the same
20
+ # will override the previous decorator, not decorate twice. This can be
21
+ # used to create idempotent class macros like +serialize+
11
22
  def decorate_attribute_type(column_name, decorator_name, &block)
12
23
  matcher = ->(name, _) { name == column_name.to_s }
13
24
  key = "_#{column_name}_#{decorator_name}"
14
25
  decorate_matching_attribute_types(matcher, key, &block)
15
26
  end
16
27
 
28
+ # This method is an internal API used to create higher level features like
29
+ # time zone aware attributes.
30
+ #
31
+ # When the schema for a model is loaded, +matcher+ will be called for each
32
+ # attribute with its name and type. If the matcher returns a truthy value,
33
+ # the type will then be yielded to the given block, and the return value
34
+ # of that block will replace the type.
35
+ #
36
+ # Subsequent calls to this method with the same value for +decorator_name+
37
+ # will replace the previous decorator, not decorate twice. This can be
38
+ # used to ensure that class macros are idempotent.
17
39
  def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
40
  reload_schema_from_cache
19
41
  decorator_name = decorator_name.to_s
@@ -24,13 +46,13 @@ module ActiveRecord
24
46
 
25
47
  private
26
48
 
27
- def load_schema!
28
- super
29
- attribute_types.each do |name, type|
30
- decorated_type = attribute_type_decorations.apply(name, type)
31
- define_attribute(name, decorated_type)
49
+ def load_schema!
50
+ super
51
+ attribute_types.each do |name, type|
52
+ decorated_type = attribute_type_decorations.apply(name, type)
53
+ define_attribute(name, decorated_type)
54
+ end
32
55
  end
33
- end
34
56
  end
35
57
 
36
58
  class TypeDecorator # :nodoc:
@@ -53,15 +75,15 @@ module ActiveRecord
53
75
 
54
76
  private
55
77
 
56
- def decorators_for(name, type)
57
- matching(name, type).map(&:last)
58
- end
78
+ def decorators_for(name, type)
79
+ matching(name, type).map(&:last)
80
+ end
59
81
 
60
- def matching(name, type)
61
- @decorations.values.select do |(matcher, _)|
62
- matcher.call(name, type)
82
+ def matching(name, type)
83
+ @decorations.values.select do |(matcher, _)|
84
+ matcher.call(name, type)
85
+ end
63
86
  end
64
- end
65
87
  end
66
88
  end
67
89
  end
@@ -1,7 +1,7 @@
1
- require 'active_support/core_ext/enumerable'
2
- require 'active_support/core_ext/string/filters'
3
- require 'mutex_m'
4
- require 'concurrent/map'
1
+ require "active_support/core_ext/enumerable"
2
+ require "active_support/core_ext/string/filters"
3
+ require "mutex_m"
4
+ require "concurrent/map"
5
5
 
6
6
  module ActiveRecord
7
7
  # = Active Record Attribute Methods
@@ -148,7 +148,7 @@ module ActiveRecord
148
148
  # Person.attribute_method?(:age=) # => true
149
149
  # Person.attribute_method?(:nothing) # => false
150
150
  def attribute_method?(attribute)
151
- super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
151
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, "")))
152
152
  end
153
153
 
154
154
  # Returns an array of column names as strings if it's not an abstract class and
@@ -161,10 +161,10 @@ module ActiveRecord
161
161
  # # => ["id", "created_at", "updated_at", "name", "age"]
162
162
  def attribute_names
163
163
  @attribute_names ||= if !abstract_class? && table_exists?
164
- attribute_types.keys
165
- else
166
- []
167
- end
164
+ attribute_types.keys
165
+ else
166
+ []
167
+ end
168
168
  end
169
169
 
170
170
  # Returns true if the given attribute exists, otherwise false.
@@ -209,13 +209,13 @@ module ActiveRecord
209
209
  # end
210
210
  #
211
211
  # person = Person.new
212
- # person.respond_to(:name) # => true
213
- # person.respond_to(:name=) # => true
214
- # person.respond_to(:name?) # => true
215
- # person.respond_to('age') # => true
216
- # person.respond_to('age=') # => true
217
- # person.respond_to('age?') # => true
218
- # person.respond_to(:nothing) # => false
212
+ # person.respond_to?(:name) # => true
213
+ # person.respond_to?(:name=) # => true
214
+ # person.respond_to?(:name?) # => true
215
+ # person.respond_to?('age') # => true
216
+ # person.respond_to?('age=') # => true
217
+ # person.respond_to?('age?') # => true
218
+ # person.respond_to?(:nothing) # => false
219
219
  def respond_to?(name, include_private = false)
220
220
  return false unless super
221
221
 
@@ -330,8 +330,6 @@ module ActiveRecord
330
330
  #
331
331
  # Note: +:id+ is always present.
332
332
  #
333
- # Alias for the #read_attribute method.
334
- #
335
333
  # class Person < ActiveRecord::Base
336
334
  # belongs_to :organization
337
335
  # end
@@ -396,65 +394,58 @@ module ActiveRecord
396
394
 
397
395
  protected
398
396
 
399
- def clone_attribute_value(reader_method, attribute_name) # :nodoc:
400
- value = send(reader_method, attribute_name)
401
- value.duplicable? ? value.clone : value
402
- rescue TypeError, NoMethodError
403
- value
404
- end
405
-
406
- def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
407
- arel_attributes_with_values(attributes_for_create(attribute_names))
408
- end
397
+ def attribute_method?(attr_name) # :nodoc:
398
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
399
+ defined?(@attributes) && @attributes.key?(attr_name)
400
+ end
409
401
 
410
- def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
411
- arel_attributes_with_values(attributes_for_update(attribute_names))
412
- end
402
+ private
413
403
 
414
- def attribute_method?(attr_name) # :nodoc:
415
- # We check defined? because Syck calls respond_to? before actually calling initialize.
416
- defined?(@attributes) && @attributes.key?(attr_name)
417
- end
404
+ def arel_attributes_with_values_for_create(attribute_names)
405
+ arel_attributes_with_values(attributes_for_create(attribute_names))
406
+ end
418
407
 
419
- private
408
+ def arel_attributes_with_values_for_update(attribute_names)
409
+ arel_attributes_with_values(attributes_for_update(attribute_names))
410
+ end
420
411
 
421
- # Returns a Hash of the Arel::Attributes and attribute values that have been
422
- # typecasted for use in an Arel insert/update method.
423
- def arel_attributes_with_values(attribute_names)
424
- attrs = {}
425
- arel_table = self.class.arel_table
412
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
413
+ # typecasted for use in an Arel insert/update method.
414
+ def arel_attributes_with_values(attribute_names)
415
+ attrs = {}
416
+ arel_table = self.class.arel_table
426
417
 
427
- attribute_names.each do |name|
428
- attrs[arel_table[name]] = typecasted_attribute_value(name)
418
+ attribute_names.each do |name|
419
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
420
+ end
421
+ attrs
429
422
  end
430
- attrs
431
- end
432
423
 
433
- # Filters the primary keys and readonly attributes from the attribute names.
434
- def attributes_for_update(attribute_names)
435
- attribute_names.reject do |name|
436
- readonly_attribute?(name)
424
+ # Filters the primary keys and readonly attributes from the attribute names.
425
+ def attributes_for_update(attribute_names)
426
+ attribute_names.reject do |name|
427
+ readonly_attribute?(name)
428
+ end
437
429
  end
438
- end
439
430
 
440
- # Filters out the primary keys, from the attribute names, when the primary
441
- # key is to be generated (e.g. the id attribute has no value).
442
- def attributes_for_create(attribute_names)
443
- attribute_names.reject do |name|
444
- pk_attribute?(name) && id.nil?
431
+ # Filters out the primary keys, from the attribute names, when the primary
432
+ # key is to be generated (e.g. the id attribute has no value).
433
+ def attributes_for_create(attribute_names)
434
+ attribute_names.reject do |name|
435
+ pk_attribute?(name) && id.nil?
436
+ end
445
437
  end
446
- end
447
438
 
448
- def readonly_attribute?(name)
449
- self.class.readonly_attributes.include?(name)
450
- end
439
+ def readonly_attribute?(name)
440
+ self.class.readonly_attributes.include?(name)
441
+ end
451
442
 
452
- def pk_attribute?(name)
453
- name == self.class.primary_key
454
- end
443
+ def pk_attribute?(name)
444
+ name == self.class.primary_key
445
+ end
455
446
 
456
- def typecasted_attribute_value(name)
457
- _read_attribute(name)
458
- end
447
+ def typecasted_attribute_value(name)
448
+ _read_attribute(name)
449
+ end
459
450
  end
460
451
  end
@@ -63,14 +63,14 @@ module ActiveRecord
63
63
 
64
64
  private
65
65
 
66
- # Handle *_before_type_cast for method_missing.
67
- def attribute_before_type_cast(attribute_name)
68
- read_attribute_before_type_cast(attribute_name)
69
- end
66
+ # Handle *_before_type_cast for method_missing.
67
+ def attribute_before_type_cast(attribute_name)
68
+ read_attribute_before_type_cast(attribute_name)
69
+ end
70
70
 
71
- def attribute_came_from_user?(attribute_name)
72
- @attributes[attribute_name].came_from_user?
73
- end
71
+ def attribute_came_from_user?(attribute_name)
72
+ @attributes[attribute_name].came_from_user?
73
+ end
74
74
  end
75
75
  end
76
76
  end
@@ -1,5 +1,6 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_record/attribute_mutation_tracker'
1
+ # frozen_string_literal: true
2
+ require "active_support/core_ext/module/attribute_accessors"
3
+ require "active_record/attribute_mutation_tracker"
3
4
 
4
5
  module ActiveRecord
5
6
  module AttributeMethods
@@ -15,6 +16,18 @@ module ActiveRecord
15
16
 
16
17
  class_attribute :partial_writes, instance_writer: false
17
18
  self.partial_writes = true
19
+
20
+ after_create { changes_internally_applied }
21
+ after_update { changes_internally_applied }
22
+
23
+ # Attribute methods for "changed in last call to save?"
24
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
25
+ attribute_method_prefix("saved_change_to_")
26
+ attribute_method_suffix("_before_last_save")
27
+
28
+ # Attribute methods for "will change if I call save?"
29
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
30
+ attribute_method_suffix("_change_to_be_saved", "_in_database")
18
31
  end
19
32
 
20
33
  # Attempts to +save+ the record and clears changed attributes if successful.
@@ -35,8 +48,8 @@ module ActiveRecord
35
48
  # <tt>reload</tt> the record and clears changed attributes.
36
49
  def reload(*)
37
50
  super.tap do
38
- @mutation_tracker = nil
39
51
  @previous_mutation_tracker = nil
52
+ clear_mutation_trackers
40
53
  @changed_attributes = HashWithIndifferentAccess.new
41
54
  end
42
55
  end
@@ -46,19 +59,26 @@ module ActiveRecord
46
59
  @attributes = self.class._default_attributes.map do |attr|
47
60
  attr.with_value_from_user(@attributes.fetch_value(attr.name))
48
61
  end
49
- @mutation_tracker = nil
62
+ clear_mutation_trackers
63
+ end
64
+
65
+ def changes_internally_applied # :nodoc:
66
+ @mutations_before_last_save = mutation_tracker
67
+ forget_attribute_assignments
68
+ @mutations_from_database = AttributeMutationTracker.new(@attributes)
50
69
  end
51
70
 
52
71
  def changes_applied
53
72
  @previous_mutation_tracker = mutation_tracker
54
73
  @changed_attributes = HashWithIndifferentAccess.new
55
- store_original_attributes
74
+ clear_mutation_trackers
56
75
  end
57
76
 
58
77
  def clear_changes_information
59
78
  @previous_mutation_tracker = nil
60
79
  @changed_attributes = HashWithIndifferentAccess.new
61
- store_original_attributes
80
+ forget_attribute_assignments
81
+ clear_mutation_trackers
62
82
  end
63
83
 
64
84
  def raw_write_attribute(attr_name, *)
@@ -80,17 +100,27 @@ module ActiveRecord
80
100
  if defined?(@cached_changed_attributes)
81
101
  @cached_changed_attributes
82
102
  else
103
+ emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)")
83
104
  super.reverse_merge(mutation_tracker.changed_values).freeze
84
105
  end
85
106
  end
86
107
 
87
108
  def changes
88
109
  cache_changed_attributes do
110
+ emit_warning_if_needed("changes", "saved_changes")
89
111
  super
90
112
  end
91
113
  end
92
114
 
93
115
  def previous_changes
116
+ unless previous_mutation_tracker.equal?(mutations_before_last_save)
117
+ ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc)
118
+ The behavior of `previous_changes` inside of after callbacks is
119
+ deprecated without replacement. In the next release of Rails,
120
+ this method inside of `after_save` will return the changes that
121
+ were just saved.
122
+ EOW
123
+ end
94
124
  previous_mutation_tracker.changes
95
125
  end
96
126
 
@@ -98,54 +128,206 @@ module ActiveRecord
98
128
  mutation_tracker.changed_in_place?(attr_name)
99
129
  end
100
130
 
101
- private
131
+ # Did this attribute change when we last saved? This method can be invoked
132
+ # as `saved_change_to_name?` instead of `saved_change_to_attribute?("name")`.
133
+ # Behaves similarly to +attribute_changed?+. This method is useful in
134
+ # after callbacks to determine if the call to save changed a certain
135
+ # attribute.
136
+ #
137
+ # ==== Options
138
+ #
139
+ # +from+ When passed, this method will return false unless the original
140
+ # value is equal to the given option
141
+ #
142
+ # +to+ When passed, this method will return false unless the value was
143
+ # changed to the given value
144
+ def saved_change_to_attribute?(attr_name, **options)
145
+ mutations_before_last_save.changed?(attr_name, **options)
146
+ end
102
147
 
103
- def mutation_tracker
104
- unless defined?(@mutation_tracker)
105
- @mutation_tracker = nil
106
- end
107
- @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
148
+ # Returns the change to an attribute during the last save. If the
149
+ # attribute was changed, the result will be an array containing the
150
+ # original value and the saved value.
151
+ #
152
+ # Behaves similarly to +attribute_change+. This method is useful in after
153
+ # callbacks, to see the change in an attribute that just occurred
154
+ #
155
+ # This method can be invoked as `saved_change_to_name` in instead of
156
+ # `saved_change_to_attribute("name")`
157
+ def saved_change_to_attribute(attr_name)
158
+ mutations_before_last_save.change_to_attribute(attr_name)
159
+ end
160
+
161
+ # Returns the original value of an attribute before the last save.
162
+ # Behaves similarly to +attribute_was+. This method is useful in after
163
+ # callbacks to get the original value of an attribute before the save that
164
+ # just occurred
165
+ def attribute_before_last_save(attr_name)
166
+ mutations_before_last_save.original_value(attr_name)
167
+ end
168
+
169
+ # Did the last call to `save` have any changes to change?
170
+ def saved_changes?
171
+ mutations_before_last_save.any_changes?
108
172
  end
109
173
 
110
- def changes_include?(attr_name)
111
- super || mutation_tracker.changed?(attr_name)
174
+ # Returns a hash containing all the changes that were just saved.
175
+ def saved_changes
176
+ mutations_before_last_save.changes
112
177
  end
113
178
 
114
- def clear_attribute_change(attr_name)
115
- mutation_tracker.forget_change(attr_name)
179
+ # Alias for `attribute_changed?`
180
+ def will_save_change_to_attribute?(attr_name, **options)
181
+ mutations_from_database.changed?(attr_name, **options)
116
182
  end
117
183
 
118
- def _update_record(*)
119
- partial_writes? ? super(keys_for_partial_write) : super
184
+ # Alias for `attribute_change`
185
+ def attribute_change_to_be_saved(attr_name)
186
+ mutations_from_database.change_to_attribute(attr_name)
120
187
  end
121
188
 
122
- def _create_record(*)
123
- partial_writes? ? super(keys_for_partial_write) : super
189
+ # Alias for `attribute_was`
190
+ def attribute_in_database(attr_name)
191
+ mutations_from_database.original_value(attr_name)
124
192
  end
125
193
 
126
- def keys_for_partial_write
127
- changed & self.class.column_names
194
+ # Alias for `changed?`
195
+ def has_changes_to_save?
196
+ mutations_from_database.any_changes?
128
197
  end
129
198
 
130
- def store_original_attributes
131
- @attributes = @attributes.map(&:forgetting_assignment)
132
- @mutation_tracker = nil
199
+ # Alias for `changes`
200
+ def changes_to_save
201
+ mutations_from_database.changes
133
202
  end
134
203
 
135
- def previous_mutation_tracker
136
- @previous_mutation_tracker ||= NullMutationTracker.instance
204
+ # Alias for `changed`
205
+ def changed_attribute_names_to_save
206
+ changes_to_save.keys
137
207
  end
138
208
 
139
- def cache_changed_attributes
140
- @cached_changed_attributes = changed_attributes
141
- yield
142
- ensure
143
- clear_changed_attributes_cache
209
+ # Alias for `changed_attributes`
210
+ def attributes_in_database
211
+ changes_to_save.transform_values(&:first)
144
212
  end
145
213
 
146
- def clear_changed_attributes_cache
147
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
214
+ def attribute_was(*)
215
+ emit_warning_if_needed("attribute_was", "attribute_before_last_save")
216
+ super
148
217
  end
218
+
219
+ def attribute_change(*)
220
+ emit_warning_if_needed("attribute_change", "saved_change_to_attribute")
221
+ super
222
+ end
223
+
224
+ def attribute_changed?(*)
225
+ emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?")
226
+ super
227
+ end
228
+
229
+ def changed?(*)
230
+ emit_warning_if_needed("changed?", "saved_changes?")
231
+ super
232
+ end
233
+
234
+ def changed(*)
235
+ emit_warning_if_needed("changed", "saved_changes.keys")
236
+ super
237
+ end
238
+
239
+ private
240
+
241
+ def mutation_tracker
242
+ unless defined?(@mutation_tracker)
243
+ @mutation_tracker = nil
244
+ end
245
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
246
+ end
247
+
248
+ def emit_warning_if_needed(method_name, new_method_name)
249
+ unless mutation_tracker.equal?(mutations_from_database)
250
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
251
+ The behavior of `#{method_name}` inside of after callbacks will
252
+ be changing in the next version of Rails. The new return value will reflect the
253
+ behavior of calling the method after `save` returned (e.g. the opposite of what
254
+ it returns now). To maintain the current behavior, use `#{new_method_name}`
255
+ instead.
256
+ EOW
257
+ end
258
+ end
259
+
260
+ def mutations_from_database
261
+ unless defined?(@mutations_from_database)
262
+ @mutations_from_database = nil
263
+ end
264
+ @mutations_from_database ||= mutation_tracker
265
+ end
266
+
267
+ def changes_include?(attr_name)
268
+ super || mutation_tracker.changed?(attr_name)
269
+ end
270
+
271
+ def clear_attribute_change(attr_name)
272
+ mutation_tracker.forget_change(attr_name)
273
+ mutations_from_database.forget_change(attr_name)
274
+ end
275
+
276
+ def attribute_will_change!(attr_name)
277
+ super
278
+ if self.class.has_attribute?(attr_name)
279
+ mutations_from_database.force_change(attr_name)
280
+ else
281
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
282
+ #{attr_name} is not an attribute known to Active Record.
283
+ This behavior is deprecated and will be removed in the next
284
+ version of Rails. If you'd like #{attr_name} to be managed
285
+ by Active Record, add `attribute :#{attr_name} to your class.
286
+ EOW
287
+ mutations_from_database.deprecated_force_change(attr_name)
288
+ end
289
+ end
290
+
291
+ def _update_record(*)
292
+ partial_writes? ? super(keys_for_partial_write) : super
293
+ end
294
+
295
+ def _create_record(*)
296
+ partial_writes? ? super(keys_for_partial_write) : super
297
+ end
298
+
299
+ def keys_for_partial_write
300
+ changed_attribute_names_to_save & self.class.column_names
301
+ end
302
+
303
+ def forget_attribute_assignments
304
+ @attributes = @attributes.map(&:forgetting_assignment)
305
+ end
306
+
307
+ def clear_mutation_trackers
308
+ @mutation_tracker = nil
309
+ @mutations_from_database = nil
310
+ @mutations_before_last_save = nil
311
+ end
312
+
313
+ def previous_mutation_tracker
314
+ @previous_mutation_tracker ||= NullMutationTracker.instance
315
+ end
316
+
317
+ def mutations_before_last_save
318
+ @mutations_before_last_save ||= previous_mutation_tracker
319
+ end
320
+
321
+ def cache_changed_attributes
322
+ @cached_changed_attributes = changed_attributes
323
+ yield
324
+ ensure
325
+ clear_changed_attributes_cache
326
+ end
327
+
328
+ def clear_changed_attributes_cache
329
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
330
+ end
149
331
  end
150
332
  end
151
333
  end