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

add logic to BooleanParser to allow for optional truthy arguments #132

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ Parsers can receive options that modify their behavior. These options are passed
input :start_date, :date, parse_format: '%Y-%m-%d'
```

`:boolean` will parse values of `true` and `1` as truthy. If another value is expected to be truthy, use the option `truth_value` to assign a custom truthy case.

```ruby
input :checkbox, :boolean, truth_value: 'yes'
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, why truth instead of truthy?

Copy link
Author

Choose a reason for hiding this comment

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

just personal preference, but can switch it to truthy

```

Comment on lines +188 to +193
Copy link
Contributor

Choose a reason for hiding this comment

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

This is interesting. It looks like there isn't really a place in the README to document which options are accepted for each parser. I think we can leave this here for now, but I'll create another issue to holistically address adding this missing documentation.

Copy link
Contributor

Choose a reason for hiding this comment

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

### Exceptions

By default, `Decanter#decant` will raise an exception when unexpected parameters are passed. To override this behavior, you can change the strict mode option to one of:
Expand Down
22 changes: 20 additions & 2 deletions lib/decanter/parser/boolean_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@ class BooleanParser < ValueParser
allow TrueClass, FalseClass

parser do |val, options|
raise Decanter::ParseError.new 'Expects a single value' if val.is_a? Array
single_value_check(val)
next if (val.nil? || val === '')
[1, '1'].include?(val) || !!/true/i.match(val.to_s)
[1, '1'].include?(val) || !!/true/i.match(val.to_s) || parse_options(val, options)
end

def self.parse_options(val, options)
raw_true_value = options.fetch(:true_value, nil)
return false if raw_true_value.nil?

single_value_check(raw_true_value)
true_value = raw_true_value.to_s.downcase

# want to avoid using values that are already implemented
accounted_for_values = ['false', 'true', '1', '0', '']
return false if accounted_for_values.include?(true_value)
chawes13 marked this conversation as resolved.
Show resolved Hide resolved

!!/#{true_value}/i.match(val.to_s)
end

def self.single_value_check(v)
raise Decanter::ParseError.new 'Expects a single value' if v.is_a? Array
end
end
end
Expand Down
46 changes: 44 additions & 2 deletions spec/decanter/parser/boolean_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,29 @@
['boolean', true],
['string', 'true'],
['string', 'True'],
['string', 'truE']
]

falses = [
['number', 0],
['number', 2],
['string', '2'],
['boolean', false],
['string', 'tru']
]

trues_with_options = [
['string', 'yes', 'string', 'yes'],
['string', 'Yes', 'string', 'yes'],
['string', 'is true', 'string', 'is true'],
['string', 'is truE', 'string', 'is True'],
['number', 3, 'number', 3],
['number', 3, 'string', '3'],
['string', '3', 'number', 3],
]

falses_with_options = [
['string', 'false', 'string', 'false'],
['string', 'false', 'string', 'False'],
['string', 'yes', 'string', ''],
]

let(:name) { :foo }
Expand Down Expand Up @@ -61,5 +75,33 @@
.to raise_error(Decanter::ParseError)
end
end

context 'returns true with options for' do
trues_with_options.each do |cond|
it "#{cond[0]}: #{cond[1]}, option: {#{cond[2]}: #{cond[3]}}" do
expect(parser.parse(name, cond[1], true_value: cond[3])).to match({name => true})
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, is the older hashrocket syntax required?

Copy link
Author

Choose a reason for hiding this comment

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

no it is not, but it seems that this spec file and some others use it. Might be worth updating all tests in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense to me! Happy to consider that out of scope here

end
end
end

context 'returns false with options for' do
falses_with_options.each do |cond|
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need an array for this or could it be just a single assertion? It looks like you arrived to that conclusion by only having one entry in the array. I think you could make this test more explicit if you kept it just to one entry.

it "#{cond[0]}: #{cond[1]}, option: {#{cond[2]}: #{cond[3]}}" do
expect(parser.parse(name, cond[1], true_value: cond[3])).to match({name => false})
end
end
end

context 'with empty string and empty options' do
it 'returns nil' do
expect(parser.parse(name, '', true_value: '')).to match({name => nil})
end
end
Comment on lines +95 to +99
Copy link
Contributor

Choose a reason for hiding this comment

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

This is an interesting edge case. Would you not expect setting true_value: "" to return true?

Copy link
Author

Choose a reason for hiding this comment

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

In the normalize method we return nil if blank

return if (value.nil? || value.blank?) # line 22

Copy link
Author

Choose a reason for hiding this comment

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

this was part of the originally functionality

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed - this also related to the them of a level of opinion in the parser


context 'with nil and nil options' do
it 'returns nil' do
expect(parser.parse(name, nil, true_value: nil)).to match({name => nil})
end
end
end
end