Skip to content

Commit b816e61

Browse files
authored
Avoid Adding Defaults for Missing Array Objects (#36)
1 parent 0247395 commit b816e61

4 files changed

Lines changed: 102 additions & 13 deletions

File tree

‎lib/mini_defender/rules/default.rb‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ def self.signature
1010
end
1111

1212
def self.make(args)
13-
raise ArgumentError, 'Expected exactly one argument.' unless args.length == 1
13+
unless args.length == 1
14+
raise ArgumentError, 'Expected exactly one argument.'
15+
end
1416

1517
new(args[0])
1618
end

‎lib/mini_defender/validator.rb‎

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,52 @@ def validate
2727
end
2828

2929
# Set default values for missing data key compared to rules
30-
data_rules.each do |k, set|
31-
if !@data.key?(k) || missing_value?(@data[k])
32-
set.filter{ |r| r.defaults?(self) }.each do |r|
33-
@data.merge!({k => r.default_value(self)}.flatten_keys(keep_roots: true))
30+
defaults_rules = data_rules.filter { |k, set| set.any? { |r| r.defaults?(self) } }
31+
defaults_rules.each do |key_pattern, set|
32+
# If element is not inside an array
33+
unless key_pattern.match?(/\*/)
34+
if missing_value?(@data[key_pattern])
35+
@data[key_pattern] = set.map { |r| r.default_value(self) }.reject(&:nil?).first
3436
end
37+
38+
next
3539
end
40+
41+
parent, sep, child_key = key_pattern.rpartition('*')
42+
parent += sep
43+
44+
# If parent or a sibling exists, add the default value
45+
pattern = Regexp.new("\\A#{parent.gsub('*', '\d')}\\z")
46+
additions = {}
47+
@data.each do |k, _|
48+
unless k.match?(pattern)
49+
next
50+
end
51+
52+
current_child_key = "#{k}#{child_key}"
53+
54+
unless missing_value?(@data[current_child_key])
55+
next
56+
end
57+
58+
additions[current_child_key] = set.map { |r| r.default_value(self) }.reject(&:nil?).first
59+
end
60+
61+
@data.merge!(additions)
3662
end
3763

3864
data_rules = @expander.expand(data_rules, @data)
39-
4065
data_rules.each do |k, rule_set|
4166
@errors[k] = []
4267

4368
value_included = true
4469
required = rule_set.any? { |r| r.implicit?(self) }
4570

46-
if !@data.key?(k) || missing_value?(@data[k])
47-
rule_set.filter{ |r| r.defaults?(self) }.each do |r|
48-
@data.merge!({k => r.default_value(self)}.flatten_keys(keep_roots: true))
71+
unless @data.key?(k)
72+
if required
73+
@errors[k] << 'This field is missing.'
4974
end
50-
end
5175

52-
unless @data.key?(k)
53-
@errors[k] << 'This field is missing.' if required
5476
next
5577
end
5678

‎lib/mini_defender/version.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module MiniDefender
4-
VERSION = "0.7.4"
4+
VERSION = "0.8.0"
55
end

‎test/rules/default_test.rb‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class DefaultTest < Minitest::Test
6+
def setup
7+
@validator = MiniDefender::Validator.new({
8+
'name' => 'required|string|max:255',
9+
10+
'laptop' => 'string|default:macbook',
11+
12+
'address' => 'required|hash',
13+
'address.street' => 'required|string|max:255',
14+
'address.additional' => 'string|max:255',
15+
'address.country' => 'required|string|max:2',
16+
'address.components' => 'required|hash',
17+
'address.components.c1' => 'required|string',
18+
'address.components.c2' => 'string|default:None',
19+
20+
'dependents' => 'array',
21+
'dependents.*.name' => 'string|max:255',
22+
'dependents.*.needs' => 'array',
23+
'dependents.*.needs.*.id' => 'integer|min:0',
24+
'dependents.*.needs.*.description' => 'string|default:None',
25+
}, {
26+
'name' => 'John Doe',
27+
'address' => {
28+
'street' => '123 Main St',
29+
'country' => 'SA',
30+
'components' => {
31+
'c1' => '1234567890',
32+
}
33+
},
34+
'dependents' => [
35+
{ 'name' => 'Jane Doe', 'needs' => [{ 'id' => 1, 'description' => 'Need 1' }] },
36+
{
37+
'name' => 'Jonny Doe', 'needs' => [
38+
{ 'id' => 2, 'description' => 'Need 2' },
39+
{ 'id' => 3, },
40+
]
41+
},
42+
{ 'name' => 'Mary Doe' },
43+
]
44+
})
45+
46+
@validator.validate!
47+
@validated = @validator.validated
48+
end
49+
50+
def test_default_value_for_root_element_must_be_set
51+
assert_equal('macbook', @validated['laptop'])
52+
end
53+
54+
def test_default_value_for_nested_element_must_be_set
55+
assert_equal('None', @validated['address']['components']['c2'])
56+
end
57+
58+
def test_default_value_for_array_element_must_be_set
59+
assert_equal('None', @validated['dependents'][1]['needs'][1]['description'])
60+
end
61+
62+
def test_default_value_for_array_element_where_parent_does_not_exist_must_not_be_set
63+
assert_nil(@validated['dependents'][2]['needs'])
64+
end
65+
end

0 commit comments

Comments
 (0)