Reputation: 32522
I'm looking for a way to simplify the code for the following logic:
.clamp
the value between a minimum and a maximumHere's the long form that works:
minimum = 1
maximum = 10_000
value = value.to_i
value = maximum if value.zero?
value = value.clamp(minimum, maximum)
So for example, if value
is ""
, I should get 10,000. If value
is "15"
, I should get 15. If value
is "45000"
, I should get 10000
.
Is there a way to shorten this logic, assuming that minimum and maximum are defined and that the default value is the maximum?
The biggest problem I've had in shortening it is that null-coalescing doesn't work on the zero, since Ruby considers zero a truthy value. Otherwise, it could be a one-liner.
Upvotes: 0
Views: 427
Reputation: 84343
If you're messing with String objects when you expect an Integer, you're probably dealing with user input. If that's the case, the problem should really be solved through input validation and/or looping over an input prompt elsewhere in your program rather than trying to perform input transformations inline.
Duck-typing is great, but I suspect you have a broken contract between methods or objects. As a general rule, it's better to fix the source of the mismatch unless you're deliberately wrapping some piece of code that shouldn't be modified. There are a number of possible refactorings and patterns if that's the case.
One such solution is to use a collaborator object or method for information hiding. This enables you to perform your input transformations without complicating your inline logic, and allowing you to access the transformed value as a simple method call such as user_input.value
.
If you are just trying to tighten up your current method you can aim for shorter code, but I'd personally recommend aiming for maintainability instead. Pragmatically, that means sending your value to the constructor of a specialized object, and then asking that object for a result. As a bonus, this allows you to use a default variable assignment to handle nil
. Consider the following:
class MaximizeUnsetInputValue
MIN = 1
MAX = 10_000
def initialize value=MAX
@value = value
set_empty_to_max
end
def set_empty_to_max
@value = MAX if @value.to_i.zero?
end
def value
@value.clamp MIN, MAX
end
end
You can easily validate that this handles your various use cases while hiding the implementation details inside the collaborator object's methods. For example:
inputs_and_expected_outputs = [
[0, 10000],
[1, 1],
[10, 10],
[10001, 10000],
[nil, 10000],
['', 10000]
]
inputs_and_expected_outputs.map do |input, expected|
MaximizeUnsetInputValue.new(input).value == expected
end
#=> [true, true, true, true, true, true]
There are certainly other approaches, but this is the one I'd recommend based on your posted code. It isn't shorter, but I think it's readable, maintainable, adaptable, and reusable. Your mileage may vary.
Upvotes: 1
Reputation: 1828
you could still do a one-liner with your current logic
minimum, maximum = 1, 10_000
value = ( value.to_i.zero? ? maximum: value.to_i ).clamp(minimum, maximum)
but not sure if your issue is that if you enter '0' you want 1 and not 10_000 if so then try this
minimum, maximum = 1, 10_000
value = (value.to_i if Float(value) rescue maximum).clamp(minimum, maximum)
Upvotes: 1