Skip to content

Commit a475c60

Browse files
committed
Unify ActiveRecord behavior for errors, callbacks, and validations across all versions (6.0, 6.1, 7.0, 7.1, 7.2, 8.0)
We found these cleanups during #859 but following #788#863 there is an initiative to simplify ActiveRecord maintenance. This streamlines a few things across all versions to make it easier to maintain. Ideally after #859 is merged, we can transfer those tests up to 8.0 as well.
1 parent 058aa1c commit a475c60

File tree

3 files changed

+84
-81
lines changed

3 files changed

+84
-81
lines changed

gems/activerecord/6.0/activerecord.rbs

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module ActiveRecord
22
class Base
3-
type condition[T] = Symbol | ^(T) [self: T] -> boolish
3+
# https://guides.rubyonrails.org/v6.0/active_record_callbacks.html#multiple-callback-conditions
4+
# https://guides.rubyonrails.org/v6.0/active_record_validations.html#combining-validation-conditions
5+
type conditions[T] = ActiveModel::Validations::ClassMethods::conditions[T]
46

57
# puts ActiveRecord::Base.singleton_class.ancestors.map(&:to_s).select { |s| /ClassMethods$/.match?(s) }.map{ |s| "extend ::#{s}" }.sort
68
extend ::ActiveModel::AttributeMethods::ClassMethods
@@ -49,8 +51,8 @@ module ActiveRecord
4951
def self.transaction: [T] (?requires_new: boolish, ?isolation: (:read_uncommitted | :read_committed | :repeatable_read | :serializable)?, ?joinable: boolish) { () -> T } -> T
5052
def self.create: (**untyped) -> instance
5153
def self.create!: (**untyped) -> instance
52-
def self.validate: (*untyped, ?if: condition[instance], ?unless: condition[instance], **untyped) -> void
53-
def self.validates: (*untyped, ?if: condition[instance], ?unless: condition[instance], **untyped) -> void
54+
def self.validate: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void
55+
def self.validates: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void
5456

5557
# callbacks
5658
interface _AfterCreateCallbackObject
@@ -96,29 +98,29 @@ module ActiveRecord
9698
type before_validation_callback[T] = callback[T] | _BeforeValidationCallbackObject
9799
type before_save_callback[T] = callback[T] | _BeforeSaveCallbackObject
98100

99-
def self.after_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
100-
def self.after_create_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
101-
def self.after_update_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
102-
def self.after_destroy_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
103-
def self.after_save_commit: (*after_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
104-
def self.after_create: (*after_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
105-
def self.after_destroy: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
106-
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
107-
def self.after_save: (*after_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
108-
def self.after_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
109-
def self.after_validation: (*after_validation_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
110-
def self.after_initialize: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
111-
def self.after_find: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
112-
def self.after_touch: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
113-
def self.around_create: (*around_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
114-
def self.around_destroy: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
115-
def self.around_save: (*around_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
116-
def self.around_update: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
117-
def self.before_create: (*before_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
118-
def self.before_destroy: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
119-
def self.before_save: (*before_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
120-
def self.before_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
121-
def self.before_validation: (*before_validation_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
101+
def self.after_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
102+
def self.after_create_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
103+
def self.after_update_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
104+
def self.after_destroy_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
105+
def self.after_save_commit: (*after_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
106+
def self.after_create: (*after_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
107+
def self.after_destroy: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
108+
def self.after_rollback: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
109+
def self.after_save: (*after_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
110+
def self.after_update: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
111+
def self.after_validation: (*after_validation_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
112+
def self.after_initialize: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
113+
def self.after_find: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
114+
def self.after_touch: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
115+
def self.around_create: (*around_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
116+
def self.around_destroy: (*around_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
117+
def self.around_save: (*around_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
118+
def self.around_update: (*around_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
119+
def self.before_create: (*before_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
120+
def self.before_destroy: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
121+
def self.before_save: (*before_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
122+
def self.before_update: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
123+
def self.before_validation: (*before_validation_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
122124

123125
def self.columns: () -> Array[untyped]
124126
def self.reflect_on_all_associations: (?Symbol) -> Array[untyped]
@@ -133,7 +135,6 @@ module ActiveRecord
133135
def destroy: () -> bool
134136
def valid?: (?Symbol | Array[Symbol] context) -> bool
135137
def invalid?: (?Symbol | Array[Symbol] context) -> bool
136-
def errors: () -> untyped
137138
def []: (Symbol) -> untyped
138139
def []=: (Symbol, untyped) -> untyped
139140

gems/activerecord/7.2/activerecord.rbs

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module ActiveRecord
22
class Base
3-
type condition[T] = Symbol | ^(T) [self: T] -> boolish
3+
# https://guides.rubyonrails.org/v7.2/active_record_callbacks.html#multiple-callback-conditions
4+
# https://guides.rubyonrails.org/v7.2/active_record_validations.html#combining-validation-conditions
5+
type conditions[T] = ActiveModel::Validations::ClassMethods::conditions[T]
46

57
# puts ActiveRecord::Base.singleton_class.ancestors.map(&:to_s).select { |s| /ClassMethods$/.match?(s) }.map{ |s| "extend ::#{s}" }.sort
68
extend ::ActiveModel::AttributeMethods::ClassMethods
@@ -49,8 +51,8 @@ module ActiveRecord
4951
def self.transaction: [T] (?requires_new: boolish, ?isolation: (:read_uncommitted | :read_committed | :repeatable_read | :serializable)?, ?joinable: boolish) { () -> T } -> T
5052
def self.create: (**untyped) -> instance
5153
def self.create!: (**untyped) -> instance
52-
def self.validate: (*untyped, ?if: condition[instance], ?unless: condition[instance], **untyped) -> void
53-
def self.validates: (*untyped, ?if: condition[instance], ?unless: condition[instance], **untyped) -> void
54+
def self.validate: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void
55+
def self.validates: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void
5456

5557
# callbacks
5658
interface _AfterCreateCallbackObject
@@ -97,29 +99,29 @@ module ActiveRecord
9799
type before_validation_callback[T] = callback[T] | _BeforeValidationCallbackObject
98100
type before_save_callback[T] = callback[T] | _BeforeSaveCallbackObject
99101

100-
def self.after_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
101-
def self.after_create_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
102-
def self.after_update_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
103-
def self.after_destroy_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
104-
def self.after_save_commit: (*after_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) -> void | ...
105-
def self.after_create: (*after_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
106-
def self.after_destroy: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
107-
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
108-
def self.after_save: (*after_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
109-
def self.after_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
110-
def self.after_validation: (*after_validation_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
111-
def self.after_initialize: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
112-
def self.after_find: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
113-
def self.after_touch: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
114-
def self.around_create: (*around_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
115-
def self.around_destroy: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
116-
def self.around_save: (*around_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
117-
def self.around_update: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
118-
def self.before_create: (*before_create_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
119-
def self.before_destroy: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
120-
def self.before_save: (*before_save_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
121-
def self.before_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
122-
def self.before_validation: (*before_validation_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
102+
def self.after_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
103+
def self.after_create_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
104+
def self.after_update_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
105+
def self.after_destroy_commit: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
106+
def self.after_save_commit: (*after_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void | ...
107+
def self.after_create: (*after_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
108+
def self.after_destroy: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
109+
def self.after_rollback: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
110+
def self.after_save: (*after_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
111+
def self.after_update: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
112+
def self.after_validation: (*after_validation_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
113+
def self.after_initialize: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
114+
def self.after_find: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
115+
def self.after_touch: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
116+
def self.around_create: (*around_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
117+
def self.around_destroy: (*around_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
118+
def self.around_save: (*around_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
119+
def self.around_update: (*around_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
120+
def self.before_create: (*before_create_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
121+
def self.before_destroy: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
122+
def self.before_save: (*before_save_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
123+
def self.before_update: (*callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
124+
def self.before_validation: (*before_validation_callback[instance], ?if: conditions[instance], ?unless: conditions[instance], **untyped) ?{ () [self: instance] -> untyped } -> void
123125

124126
def self.columns: () -> Array[untyped]
125127
def self.reflect_on_all_associations: (?Symbol) -> Array[untyped]
@@ -134,7 +136,6 @@ module ActiveRecord
134136
def destroy: () -> bool
135137
def valid?: (?Symbol | Array[Symbol] context) -> bool
136138
def invalid?: (?Symbol | Array[Symbol] context) -> bool
137-
def errors: () -> untyped
138139
def []: (Symbol) -> untyped
139140
def []=: (Symbol, untyped) -> untyped
140141

0 commit comments

Comments
 (0)