Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow_nil should work for casted value in NumericalityValidator #40765

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions activemodel/lib/active_model/validations/numericality.rb
Expand Up @@ -109,7 +109,9 @@ def allow_only_integer?(record)
end

def read_attribute_for_validation(record, attr_name)
return super if record_attribute_changed_in_place?(record, attr_name)
value = super

return value if record_attribute_changed_in_place?(record, attr_name)

came_from_user = :"#{attr_name}_came_from_user?"

Expand All @@ -126,7 +128,7 @@ def read_attribute_for_validation(record, attr_name)
end
end

raw_value || super
raw_value || value
end

def record_attribute_changed_in_place?(record, attr_name)
Expand Down
11 changes: 7 additions & 4 deletions activemodel/lib/active_model/validator.rb
Expand Up @@ -147,9 +147,10 @@ def initialize(options)
# override +validate_each+ with validation logic.
def validate(record)
attributes.each do |attribute|
value = read_attribute_for_validation(record, attribute)
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
validate_each(record, attribute, value)
catch(:allowed) do
value = read_attribute_for_validation(record, attribute)
validate_each(record, attribute, value)
end
end
end

Expand All @@ -167,7 +168,9 @@ def check_validity!

private
def read_attribute_for_validation(record, attr_name)
record.read_attribute_for_validation(attr_name)
value = record.read_attribute_for_validation(attr_name)
throw(:allowed) if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the checks here actually make the API for custom validations worse. Users need to know that they need to deal with those options inside read_attribute_for_validation while before they didn't. I believe we should continue doing this check inside validate to keep users not needing to worry about checking for those options.

Can we split the job of read_attribute_for_validation in two? One which does record.read_attribute_for_validation(attr_name) and we use for the options checks and the other than calculate value that we will pass to validate_each? This will also avoid the throw/abort.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is not ideal, the intent of the implementation was to avoid extra value argument for read_attribute_for_validation.
I've given up that finally, splited the job and add value argument for read_attribute_for_validation in 720a60e (perhaps read_attribute_for_validation no longer appropriate name now?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree the name is not matching the behaviour anymore. I'd call it prepare_value_for_validation.

value
end
end

Expand Down
Expand Up @@ -110,4 +110,12 @@ def test_aliased_attribute

assert_not_predicate subject, :valid?
end

def test_allow_nil_works_for_casted_value
model_class.validates_numericality_of(:bank_balance, greater_than: 0, allow_nil: true)

subject = model_class.new(bank_balance: "")

assert_predicate subject, :valid?
end
end