Skip to content

Commit 9dce1a0

Browse files
Handle length validator with procs, improve validators support (#1859)
Devise changed their password validations to use a proc for minimum/maximum for additional customization, but that apparently broke SimpleForm minlength/maxlength logic since it wasn't handling that. Instead of implementing it custom by checking for the value responding to `call` like we do for a couple of other validators, I went with the route of copying over the `resolve_value` implementation added to Rails 7.1 that handles all the possible scenarios for procs / send / etc, which allows different ways of setting up the proc. Expand usage of the shared `resolve_value` implementation across the board, so other validations like numericality and format apply the same logic to better match Rails behavior. Closes #1858
1 parent 584127a commit 9dce1a0

File tree

9 files changed

+51
-28
lines changed

9 files changed

+51
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## Unreleased
22

33
* Ruby 4.0 support (no changes required)
4+
* Support procs on validators for minlength/maxlength, and improve validators logic across the board to match Rails [#1859](https://github.com/heartcombo/simple_form/pull/1859)
45

56
## 5.4.0
67

lib/simple_form/components/maxlength.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def find_length_validator
2626

2727
def maximum_length_value_from(length_validator)
2828
if length_validator
29-
length_validator.options[:is] || length_validator.options[:maximum]
29+
value = length_validator.options[:is] || length_validator.options[:maximum]
30+
resolve_validator_value(value)
3031
end
3132
end
3233
end

lib/simple_form/components/min_max.rb

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,23 @@ def integer?
1919

2020
def minimum_value(validator_options)
2121
if integer? && validator_options.key?(:greater_than)
22-
evaluate_numericality_validator_option(validator_options[:greater_than]) + 1
22+
resolve_validator_value(validator_options[:greater_than]) + 1
2323
else
24-
evaluate_numericality_validator_option(validator_options[:greater_than_or_equal_to])
24+
resolve_validator_value(validator_options[:greater_than_or_equal_to])
2525
end
2626
end
2727

2828
def maximum_value(validator_options)
2929
if integer? && validator_options.key?(:less_than)
30-
evaluate_numericality_validator_option(validator_options[:less_than]) - 1
30+
resolve_validator_value(validator_options[:less_than]) - 1
3131
else
32-
evaluate_numericality_validator_option(validator_options[:less_than_or_equal_to])
32+
resolve_validator_value(validator_options[:less_than_or_equal_to])
3333
end
3434
end
3535

3636
def find_numericality_validator
3737
find_validator(:numericality)
3838
end
39-
40-
def evaluate_numericality_validator_option(option)
41-
if option.is_a?(Numeric)
42-
option
43-
elsif option.is_a?(Symbol)
44-
object.send(option)
45-
elsif option.respond_to?(:call)
46-
option.call(object)
47-
end
48-
end
4939
end
5040
end
5141
end

lib/simple_form/components/minlength.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def find_length_validator
2626

2727
def minimum_length_value_from(length_validator)
2828
if length_validator
29-
length_validator.options[:is] || length_validator.options[:minimum]
29+
value = length_validator.options[:is] || length_validator.options[:minimum]
30+
resolve_validator_value(value)
3031
end
3132
end
3233
end

lib/simple_form/components/pattern.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,13 @@ def pattern_source
1515
if pattern.is_a?(String)
1616
pattern
1717
elsif (pattern_validator = find_pattern_validator) && (with = pattern_validator.options[:with])
18-
evaluate_format_validator_option(with).source
18+
resolve_validator_value(with).source
1919
end
2020
end
2121

2222
def find_pattern_validator
2323
find_validator(:format)
2424
end
25-
26-
def evaluate_format_validator_option(option)
27-
if option.respond_to?(:call)
28-
option.call(object)
29-
else
30-
option
31-
end
32-
end
3325
end
3426
end
3527
end

lib/simple_form/helpers/validators.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ def action_validator_match?(validator)
4040
def find_validator(kind)
4141
attribute_validators.find { |v| v.kind == kind } if has_validators?
4242
end
43+
44+
# Implements `ActiveModel::Validations::ResolveValue`, introduced by Rails 7.1.
45+
# https://github.com/rails/rails/blob/v7.1.0/activemodel/lib/active_model/validations/resolve_value.rb
46+
def resolve_validator_value(value)
47+
case value
48+
when Proc
49+
if value.arity == 0
50+
value.call
51+
else
52+
value.call(object)
53+
end
54+
when Symbol
55+
object.send(value)
56+
else
57+
if value.respond_to?(:call)
58+
value.call(object)
59+
else
60+
value
61+
end
62+
end
63+
end
4364
end
4465
end
4566
end

test/inputs/string_input_test.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,26 @@ class StringInputTest < ActionView::TestCase
3333
assert_select 'input.password[type=password][maxlength="100"]'
3434
end
3535

36-
test 'input infers maxlength column definition from validation when present' do
36+
test 'input infers maxlength from validation when present' do
3737
with_input_for @validating_user, :name, :string
3838
assert_select 'input.string[maxlength="25"]'
3939
end
4040

41-
test 'input infers minlength column definition from validation when present' do
41+
test 'input infers minlength from validation when present' do
4242
with_input_for @validating_user, :name, :string
4343
assert_select 'input.string[minlength="5"]'
4444
end
4545

46+
test 'input infers maxlength from validation proc when present' do
47+
with_input_for @validating_user, :username, :string
48+
assert_select 'input.string[maxlength="30"]'
49+
end
50+
51+
test 'input infers minlength from validation proc when present' do
52+
with_input_for @validating_user, :username, :string
53+
assert_select 'input.string[minlength="10"]'
54+
end
55+
4656
test 'input gets maxlength from validation when :is option present' do
4757
with_input_for @validating_user, :home_picture, :string
4858
assert_select 'input.string[maxlength="12"]'

test/support/models.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class User
8888
extend ActiveModel::Naming
8989
include ActiveModel::Conversion
9090

91-
attr_accessor :id, :name, :company, :company_id, :time_zone, :active, :age,
91+
attr_accessor :id, :name, :username, :company, :company_id, :time_zone, :active, :age,
9292
:description, :created_at, :updated_at, :credit_limit, :password, :url,
9393
:delivery_time, :born_at, :special_company_id, :country, :tags, :tag_ids,
9494
:avatar, :home_picture, :email, :status, :residence_country, :phone_number,
@@ -261,6 +261,7 @@ def self.readonly_attributes
261261
class ValidatingUser < User
262262
include ActiveModel::Validations
263263
validates :name, presence: true
264+
validates :username, presence: true
264265
validates :company, presence: true
265266
validates :age, presence: true, if: proc { |user| user.name }
266267
validates :amount, presence: true, unless: proc { |user| user.age }
@@ -282,6 +283,11 @@ class ValidatingUser < User
282283
less_than_or_equal_to: :max_attempts,
283284
only_integer: true
284285
validates_length_of :name, maximum: 25, minimum: 5
286+
if ActiveModel.gem_version >= Gem::Version::new("7.1.0")
287+
validates_length_of :username, maximum: -> { 30 }, minimum: -> { 10 }
288+
else
289+
validates_length_of :username, maximum: ->(_) { 30 }, minimum: ->(_) { 10 }
290+
end
285291
validates_length_of :description, in: 15..50
286292
validates_length_of :home_picture, is: 12
287293

test/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def setup_users(extra_attributes = {})
5656

5757
@validating_user = ValidatingUser.build({
5858
name: 'Tester McTesterson',
59+
username: 'mctesterson',
5960
description: 'A test user of the most distinguished caliber',
6061
home_picture: 'Home picture',
6162
age: 19,

0 commit comments

Comments
 (0)