activerecord 6.0.0.beta1 → 6.0.1.rc1

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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +27 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. data/lib/arel/nodes/values.rb +0 -16
@@ -35,7 +35,8 @@ module ActiveRecord
35
35
  end
36
36
 
37
37
  def initialize_generated_modules # :nodoc:
38
- @generated_attribute_methods = GeneratedAttributeMethods.new
38
+ @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
39
+ private_constant :GeneratedAttributeMethods
39
40
  @attribute_methods_generated = false
40
41
  include @generated_attribute_methods
41
42
 
@@ -158,57 +159,6 @@ module ActiveRecord
158
159
  end
159
160
  end
160
161
 
161
- # Regexp for column names (with or without a table name prefix). Matches
162
- # the following:
163
- # "#{table_name}.#{column_name}"
164
- # "#{column_name}"
165
- COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
166
-
167
- # Regexp for column names with order (with or without a table name
168
- # prefix, with or without various order modifiers). Matches the following:
169
- # "#{table_name}.#{column_name}"
170
- # "#{table_name}.#{column_name} #{direction}"
171
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
172
- # "#{table_name}.#{column_name} NULLS LAST"
173
- # "#{column_name}"
174
- # "#{column_name} #{direction}"
175
- # "#{column_name} #{direction} NULLS FIRST"
176
- # "#{column_name} NULLS LAST"
177
- COLUMN_NAME_WITH_ORDER = /
178
- \A
179
- (?:\w+\.)?
180
- \w+
181
- (?:\s+asc|\s+desc)?
182
- (?:\s+nulls\s+(?:first|last))?
183
- \z
184
- /ix
185
-
186
- def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
187
- unexpected = args.reject do |arg|
188
- Arel.arel_node?(arg) ||
189
- arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
190
- end
191
-
192
- return if unexpected.none?
193
-
194
- if allow_unsafe_raw_sql == :deprecated
195
- ActiveSupport::Deprecation.warn(
196
- "Dangerous query method (method whose arguments are used as raw " \
197
- "SQL) called with non-attribute argument(s): " \
198
- "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
199
- "arguments will be disallowed in Rails 6.0. This method should " \
200
- "not be called with user-provided values, such as request " \
201
- "parameters or model attributes. Known-safe values can be passed " \
202
- "by wrapping them in Arel.sql()."
203
- )
204
- else
205
- raise(ActiveRecord::UnknownAttributeReference,
206
- "Query method called with non-attribute argument(s): " +
207
- unexpected.map(&:inspect).join(", ")
208
- )
209
- end
210
- end
211
-
212
162
  # Returns true if the given attribute exists, otherwise false.
213
163
  #
214
164
  # class Person < ActiveRecord::Base
@@ -464,7 +414,7 @@ module ActiveRecord
464
414
  end
465
415
 
466
416
  def pk_attribute?(name)
467
- name == self.class.primary_key
417
+ name == @primary_key
468
418
  end
469
419
  end
470
420
  end
@@ -46,6 +46,7 @@ module ActiveRecord
46
46
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
47
47
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
48
48
  def read_attribute_before_type_cast(attr_name)
49
+ sync_with_transaction_state if @transaction_state&.finalized?
49
50
  @attributes[attr_name.to_s].value_before_type_cast
50
51
  end
51
52
 
@@ -60,17 +61,19 @@ module ActiveRecord
60
61
  # task.attributes_before_type_cast
61
62
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
62
63
  def attributes_before_type_cast
64
+ sync_with_transaction_state if @transaction_state&.finalized?
63
65
  @attributes.values_before_type_cast
64
66
  end
65
67
 
66
68
  private
67
69
 
68
- # Handle *_before_type_cast for method_missing.
70
+ # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
69
71
  def attribute_before_type_cast(attribute_name)
70
72
  read_attribute_before_type_cast(attribute_name)
71
73
  end
72
74
 
73
75
  def attribute_came_from_user?(attribute_name)
76
+ sync_with_transaction_state if @transaction_state&.finalized?
74
77
  @attributes[attribute_name].came_from_user?
75
78
  end
76
79
  end
@@ -29,9 +29,7 @@ module ActiveRecord
29
29
  # <tt>reload</tt> the record and clears changed attributes.
30
30
  def reload(*)
31
31
  super.tap do
32
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
33
32
  @mutations_before_last_save = nil
34
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
35
33
  @mutations_from_database = nil
36
34
  end
37
35
  end
@@ -51,7 +49,7 @@ module ActiveRecord
51
49
  # +to+ When passed, this method will return false unless the value was
52
50
  # changed to the given value
53
51
  def saved_change_to_attribute?(attr_name, **options)
54
- mutations_before_last_save.changed?(attr_name, **options)
52
+ mutations_before_last_save.changed?(attr_name.to_s, options)
55
53
  end
56
54
 
57
55
  # Returns the change to an attribute during the last save. If the
@@ -63,7 +61,7 @@ module ActiveRecord
63
61
  # invoked as +saved_change_to_name+ instead of
64
62
  # <tt>saved_change_to_attribute("name")</tt>.
65
63
  def saved_change_to_attribute(attr_name)
66
- mutations_before_last_save.change_to_attribute(attr_name)
64
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
67
65
  end
68
66
 
69
67
  # Returns the original value of an attribute before the last save.
@@ -73,7 +71,7 @@ module ActiveRecord
73
71
  # invoked as +name_before_last_save+ instead of
74
72
  # <tt>attribute_before_last_save("name")</tt>.
75
73
  def attribute_before_last_save(attr_name)
76
- mutations_before_last_save.original_value(attr_name)
74
+ mutations_before_last_save.original_value(attr_name.to_s)
77
75
  end
78
76
 
79
77
  # Did the last call to +save+ have any changes to change?
@@ -101,7 +99,7 @@ module ActiveRecord
101
99
  # +to+ When passed, this method will return false unless the value will be
102
100
  # changed to the given value
103
101
  def will_save_change_to_attribute?(attr_name, **options)
104
- mutations_from_database.changed?(attr_name, **options)
102
+ mutations_from_database.changed?(attr_name.to_s, options)
105
103
  end
106
104
 
107
105
  # Returns the change to an attribute that will be persisted during the
@@ -115,7 +113,7 @@ module ActiveRecord
115
113
  # If the attribute will change, the result will be an array containing the
116
114
  # original value and the new value about to be saved.
117
115
  def attribute_change_to_be_saved(attr_name)
118
- mutations_from_database.change_to_attribute(attr_name)
116
+ mutations_from_database.change_to_attribute(attr_name.to_s)
119
117
  end
120
118
 
121
119
  # Returns the value of an attribute in the database, as opposed to the
@@ -127,7 +125,7 @@ module ActiveRecord
127
125
  # saved. It can be invoked as +name_in_database+ instead of
128
126
  # <tt>attribute_in_database("name")</tt>.
129
127
  def attribute_in_database(attr_name)
130
- mutations_from_database.original_value(attr_name)
128
+ mutations_from_database.original_value(attr_name.to_s)
131
129
  end
132
130
 
133
131
  # Will the next call to +save+ have any changes to persist?
@@ -158,16 +156,51 @@ module ActiveRecord
158
156
  end
159
157
 
160
158
  private
159
+ def mutations_from_database
160
+ sync_with_transaction_state if @transaction_state&.finalized?
161
+ super
162
+ end
163
+
164
+ def mutations_before_last_save
165
+ sync_with_transaction_state if @transaction_state&.finalized?
166
+ super
167
+ end
168
+
161
169
  def write_attribute_without_type_cast(attr_name, value)
162
- name = attr_name.to_s
163
- if self.class.attribute_alias?(name)
164
- name = self.class.attribute_alias(name)
165
- end
166
- result = super(name, value)
167
- clear_attribute_change(name)
170
+ result = super
171
+ clear_attribute_change(attr_name)
168
172
  result
169
173
  end
170
174
 
175
+ def _touch_row(attribute_names, time)
176
+ @_touch_attr_names = Set.new(attribute_names)
177
+
178
+ affected_rows = super
179
+
180
+ if @_skip_dirty_tracking ||= false
181
+ clear_attribute_changes(@_touch_attr_names)
182
+ return affected_rows
183
+ end
184
+
185
+ changes = {}
186
+ @attributes.keys.each do |attr_name|
187
+ next if @_touch_attr_names.include?(attr_name)
188
+
189
+ if attribute_changed?(attr_name)
190
+ changes[attr_name] = _read_attribute(attr_name)
191
+ _write_attribute(attr_name, attribute_was(attr_name))
192
+ clear_attribute_change(attr_name)
193
+ end
194
+ end
195
+
196
+ changes_applied
197
+ changes.each { |attr_name, value| _write_attribute(attr_name, value) }
198
+
199
+ affected_rows
200
+ ensure
201
+ @_touch_attr_names, @_skip_dirty_tracking = nil, nil
202
+ end
203
+
171
204
  def _update_record(attribute_names = attribute_names_for_partial_writes)
172
205
  affected_rows = super
173
206
  changes_applied
@@ -16,40 +16,32 @@ module ActiveRecord
16
16
 
17
17
  # Returns the primary key column's value.
18
18
  def id
19
- sync_with_transaction_state
20
- primary_key = self.class.primary_key
21
- _read_attribute(primary_key) if primary_key
19
+ _read_attribute(@primary_key)
22
20
  end
23
21
 
24
22
  # Sets the primary key column's value.
25
23
  def id=(value)
26
- sync_with_transaction_state
27
- primary_key = self.class.primary_key
28
- _write_attribute(primary_key, value) if primary_key
24
+ _write_attribute(@primary_key, value)
29
25
  end
30
26
 
31
27
  # Queries the primary key column's value.
32
28
  def id?
33
- sync_with_transaction_state
34
- query_attribute(self.class.primary_key)
29
+ query_attribute(@primary_key)
35
30
  end
36
31
 
37
32
  # Returns the primary key column's value before type cast.
38
33
  def id_before_type_cast
39
- sync_with_transaction_state
40
- read_attribute_before_type_cast(self.class.primary_key)
34
+ read_attribute_before_type_cast(@primary_key)
41
35
  end
42
36
 
43
37
  # Returns the primary key column's previous value.
44
38
  def id_was
45
- sync_with_transaction_state
46
- attribute_was(self.class.primary_key)
39
+ attribute_was(@primary_key)
47
40
  end
48
41
 
49
42
  # Returns the primary key column's value from the database.
50
43
  def id_in_database
51
- sync_with_transaction_state
52
- attribute_in_database(self.class.primary_key)
44
+ attribute_in_database(@primary_key)
53
45
  end
54
46
 
55
47
  private
@@ -122,7 +114,7 @@ module ActiveRecord
122
114
  #
123
115
  # Project.primary_key # => "foo_id"
124
116
  def primary_key=(value)
125
- @primary_key = value && value.to_s
117
+ @primary_key = value && -value.to_s
126
118
  @quoted_primary_key = nil
127
119
  @attributes_builder = nil
128
120
  end
@@ -16,8 +16,7 @@ module ActiveRecord
16
16
  when true then true
17
17
  when false, nil then false
18
18
  else
19
- column = self.class.columns_hash[attr_name]
20
- if column.nil?
19
+ if !type_for_attribute(attr_name) { false }
21
20
  if Numeric === value || value !~ /[^0-9]/
22
21
  !value.to_i.zero?
23
22
  else
@@ -33,7 +32,7 @@ module ActiveRecord
33
32
  end
34
33
 
35
34
  private
36
- # Handle *? for method_missing.
35
+ # Dispatch target for <tt>*?</tt> attribute methods.
37
36
  def attribute?(attribute_name)
38
37
  query_attribute(attribute_name)
39
38
  end
@@ -9,14 +9,11 @@ module ActiveRecord
9
9
  private
10
10
 
11
11
  def define_method_attribute(name)
12
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
13
-
14
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
15
13
  generated_attribute_methods, name
16
14
  ) do |temp_method_name, attr_name_expr|
17
15
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
18
16
  def #{temp_method_name}
19
- #{sync_with_transaction_state}
20
17
  name = #{attr_name_expr}
21
18
  _read_attribute(name) { |n| missing_attribute(n, caller) }
22
19
  end
@@ -30,19 +27,16 @@ module ActiveRecord
30
27
  # to a date object, like Date.new(2004, 12, 12)).
31
28
  def read_attribute(attr_name, &block)
32
29
  name = attr_name.to_s
33
- if self.class.attribute_alias?(name)
34
- name = self.class.attribute_alias(name)
35
- end
30
+ name = self.class.attribute_aliases[name] || name
36
31
 
37
- primary_key = self.class.primary_key
38
- name = primary_key if name == "id" && primary_key
39
- sync_with_transaction_state if name == primary_key
32
+ name = @primary_key if name == "id" && @primary_key
40
33
  _read_attribute(name, &block)
41
34
  end
42
35
 
43
36
  # This method exists to avoid the expensive primary_key check internally, without
44
37
  # breaking compatibility with the read_attribute API
45
38
  def _read_attribute(attr_name, &block) # :nodoc
39
+ sync_with_transaction_state if @transaction_state&.finalized?
46
40
  @attributes.fetch_value(attr_name.to_s, &block)
47
41
  end
48
42
 
@@ -13,15 +13,12 @@ module ActiveRecord
13
13
  private
14
14
 
15
15
  def define_method_attribute=(name)
16
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
17
-
18
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
19
17
  generated_attribute_methods, name, writer: true,
20
18
  ) do |temp_method_name, attr_name_expr|
21
19
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
22
20
  def #{temp_method_name}(value)
23
21
  name = #{attr_name_expr}
24
- #{sync_with_transaction_state}
25
22
  _write_attribute(name, value)
26
23
  end
27
24
  RUBY
@@ -34,31 +31,28 @@ module ActiveRecord
34
31
  # turned into +nil+.
35
32
  def write_attribute(attr_name, value)
36
33
  name = attr_name.to_s
37
- if self.class.attribute_alias?(name)
38
- name = self.class.attribute_alias(name)
39
- end
34
+ name = self.class.attribute_aliases[name] || name
40
35
 
41
- primary_key = self.class.primary_key
42
- name = primary_key if name == "id" && primary_key
43
- sync_with_transaction_state if name == primary_key
36
+ name = @primary_key if name == "id" && @primary_key
44
37
  _write_attribute(name, value)
45
38
  end
46
39
 
47
40
  # This method exists to avoid the expensive primary_key check internally, without
48
41
  # breaking compatibility with the write_attribute API
49
42
  def _write_attribute(attr_name, value) # :nodoc:
43
+ sync_with_transaction_state if @transaction_state&.finalized?
50
44
  @attributes.write_from_user(attr_name.to_s, value)
51
45
  value
52
46
  end
53
47
 
54
48
  private
55
49
  def write_attribute_without_type_cast(attr_name, value)
56
- name = attr_name.to_s
57
- @attributes.write_cast_value(name, value)
50
+ sync_with_transaction_state if @transaction_state&.finalized?
51
+ @attributes.write_cast_value(attr_name.to_s, value)
58
52
  value
59
53
  end
60
54
 
61
- # Handle *= for method_missing.
55
+ # Dispatch target for <tt>*=</tt> attribute methods.
62
56
  def attribute=(attribute_name, value)
63
57
  _write_attribute(attribute_name, value)
64
58
  end
@@ -41,6 +41,9 @@ module ActiveRecord
41
41
  # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
42
  # examples below).
43
43
  #
44
+ # When using a symbol for +cast_type+, extra options are forwarded to the
45
+ # constructor of the type object.
46
+ #
44
47
  # ==== Examples
45
48
  #
46
49
  # The type detected by Active Record can be overridden.
@@ -112,6 +115,16 @@ module ActiveRecord
112
115
  # my_float_range: 1.0..3.5
113
116
  # }
114
117
  #
118
+ # Passing options to the type constructor
119
+ #
120
+ # # app/models/my_model.rb
121
+ # class MyModel < ActiveRecord::Base
122
+ # attribute :small_int, :integer, limit: 2
123
+ # end
124
+ #
125
+ # MyModel.create(small_int: 65537)
126
+ # # => Error: 65537 is out of range for the limit of two bytes
127
+ #
115
128
  # ==== Creating Custom Types
116
129
  #
117
130
  # Users may also define their own custom types, as long as they respond
@@ -272,7 +272,7 @@ module ActiveRecord
272
272
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
273
  # unless the parent is/was a new record itself.
274
274
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
275
+ if new_record || custom_validation_context?
276
276
  association && association.target
277
277
  elsif autosave
278
278
  association.target.find_all(&:changed_for_autosave?)
@@ -304,7 +304,7 @@ module ActiveRecord
304
304
  def validate_single_association(reflection)
305
305
  association = association_instance_get(reflection.name)
306
306
  record = association && association.reader
307
- association_valid?(reflection, record) if record
307
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
308
  end
309
309
 
310
310
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
  def association_valid?(reflection, record, index = nil)
325
325
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
326
326
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
327
+ context = validation_context if custom_validation_context?
328
328
 
329
329
  unless valid = record.valid?(context)
330
330
  if reflection.options[:autosave]
@@ -382,10 +382,14 @@ module ActiveRecord
382
382
  if association = association_instance_get(reflection.name)
383
383
  autosave = reflection.options[:autosave]
384
384
 
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
385
389
  # reconstruct the scope now that we know the owner's id
386
390
  association.reset_scope
387
391
 
388
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
389
393
  if autosave
390
394
  records_to_destroy = records.select(&:marked_for_destruction?)
391
395
  records_to_destroy.each { |record| association.destroy(record) }
@@ -397,7 +401,7 @@ module ActiveRecord
397
401
 
398
402
  saved = true
399
403
 
400
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
401
405
  if autosave
402
406
  saved = association.insert_record(record, false)
403
407
  elsif !reflection.nested?
@@ -412,7 +416,7 @@ module ActiveRecord
412
416
  saved = record.save(validate: false)
413
417
  end
414
418
 
415
- raise ActiveRecord::Rollback unless saved
419
+ raise(RecordInvalid.new(association.owner)) unless saved
416
420
  end
417
421
  end
418
422
  end
@@ -457,10 +461,16 @@ module ActiveRecord
457
461
  # If the record is new or it has changed, returns true.
458
462
  def record_changed?(reflection, record, key)
459
463
  record.new_record? ||
460
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
464
+ association_foreign_key_changed?(reflection, record, key) ||
461
465
  record.will_save_change_to_attribute?(reflection.foreign_key)
462
466
  end
463
467
 
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
472
+ end
473
+
464
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
465
475
  #
466
476
  # In addition, it will destroy the association if it was marked for destruction.
@@ -489,6 +499,10 @@ module ActiveRecord
489
499
  end
490
500
  end
491
501
 
502
+ def custom_validation_context?
503
+ validation_context && [:create, :update].exclude?(validation_context)
504
+ end
505
+
492
506
  def _ensure_no_duplicate_errors
493
507
  errors.messages.each_key do |attribute|
494
508
  errors[attribute].uniq!