Skip to content

Commit

Permalink
fix(activemodel): Divide type definition of ActiveModel::Errors#add t…
Browse files Browse the repository at this point in the history
…o 6.0 and 7.0

ActiveModel::Error was added on 6.1.

cf. rails/rails@ef68d3e

On 6.1 or later, ActiveModel::Errors#add returns ActiveModel::Error.

On the other hand, `ActiveModel::Errors#add` returns messages array on rails 6.0, because last evaluated is `Array#<<`.

cf. https://github.com/rails/rails/blob/v6.0.0/activemodel/lib/active_model/errors.rb#L311-L322

Currently in gem_rbs_collection, activemodel type definitions are 6.0 and 7.0, not 6.1.

cf. #615

So I divide type definition of ActiveModel::Errors#add to 6.0 and 7.0.
  • Loading branch information
sanfrecce-osaka committed Nov 24, 2024
1 parent fe5bb2d commit 4981ed6
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 50 deletions.
46 changes: 46 additions & 0 deletions gems/activemodel/6.0/activemodel-6.0.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module ActiveModel
class Errors
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
# More than one error can be added to the same +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
# person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
# # => {:name=>["is invalid", "must be implemented"]}
#
# person.errors.details
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
#
# If +message+ is a proc, it will be called, allowing for things like
# <tt>Time.now</tt> to be used within an error.
#
# If the <tt>:strict</tt> option is set to +true+, it will raise
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
# person.errors.add(:base, :name_or_email_blank,
# message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
# person.errors.details
# # => {:base=>[{error: :name_or_email_blank}]}
def add: (untyped attribute, ?::Symbol | ::String | ^(untyped base, ::Hash[untyped, untyped]) -> (::Symbol | ::String) type, ?::Hash[untyped, untyped] options) -> Array[String]
end
end
47 changes: 0 additions & 47 deletions gems/activemodel/6.0/activemodel.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -498,50 +498,3 @@ module ActiveModel
end
end
end

module ActiveModel
class Errors
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
# More than one error can be added to the same +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
# person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
# # => {:name=>["is invalid", "must be implemented"]}
#
# person.errors.details
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
#
# If +message+ is a proc, it will be called, allowing for things like
# <tt>Time.now</tt> to be used within an error.
#
# If the <tt>:strict</tt> option is set to +true+, it will raise
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
# person.errors.add(:base, :name_or_email_blank,
# message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
# person.errors.details
# # => {:base=>[{error: :name_or_email_blank}]}
def add: (untyped attribute, ?::Symbol | ::String | ^(untyped base, ::Hash[untyped, untyped]) -> (::Symbol | ::String) type, ?::Hash[untyped, untyped] options) -> void
end
end
22 changes: 19 additions & 3 deletions gems/activemodel/7.0/_test/test.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
require "active_model"

class Person
include ActiveModel::Model
include ActiveModel::Validations

# @dynamic name, name=
attr_accessor :name
# @dynamic name, name=, email, email=
attr_accessor :name, :email

validate :should_be_satisfied_special_email_rule

def should_be_satisfied_special_email_rule
return unless email

if Time.current >= Time.zone.local(2024, 10)
errors.add(:email, -> (_person, _options) { "must be satisfied at least 3 rules after #{Time.zone.local(2024, 10)}" }) if [/a-z/, /A-Z/, /0-9/, /[+]/].count {|rule| email.match?(rule) } > 3
else
errors.add(:email, 'must be satisfied at least 2 rules') if [/a-z/, /A-Z/, /0-9/].count {|rule| email.match?(rule) } > 2
end
end
end

ActiveModel::Error::CALLBACKS_OPTIONS
ActiveModel::Error::MESSAGE_OPTIONS

error = ActiveModel::Error.new(Person.new, :name, :too_short, count: 5)
person = Person.new(name: 'John Doe')
person.valid?

error = ActiveModel::Error.new(person, :name, :too_short, count: 10)
error.attribute
error.type
error.options
Expand Down
4 changes: 4 additions & 0 deletions gems/activemodel/7.0/_test/test.rbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class Person
include ActiveModel::Model
include ActiveModel::Validations
extend ActiveModel::Validations::ClassMethods

attr_accessor name: String?
attr_accessor email: String?

def should_be_satisfied_special_email_rule: () -> void
end
47 changes: 47 additions & 0 deletions gems/activemodel/7.0/activemodel-7.0.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,53 @@ module ActiveModel
end
end

module ActiveModel
class Errors
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
# More than one error can be added to the same +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
# person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
# # => {:name=>["is invalid", "must be implemented"]}
#
# person.errors.details
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
#
# If +message+ is a proc, it will be called, allowing for things like
# <tt>Time.now</tt> to be used within an error.
#
# If the <tt>:strict</tt> option is set to +true+, it will raise
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
# person.errors.add(:base, :name_or_email_blank,
# message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
# person.errors.details
# # => {:base=>[{error: :name_or_email_blank}]}
def add: (untyped attribute, ?::Symbol | ::String | ^(untyped base, ::Hash[untyped, untyped]) -> (::Symbol | ::String) type, ?::Hash[untyped, untyped] options) -> Error
end
end

module ActiveModel
class Error
CALLBACKS_OPTIONS: ::Array[:if | :unless | :on | :allow_nil | :allow_blank | :strict]
Expand Down

0 comments on commit 4981ed6

Please sign in to comment.