Inline Transforms
Ruby is an incredibly expressive (and easy to read!) language, but lacks a way for you to transform a value inline without resorting to nested ternaries (🫠). This gem allows you to do exactly that in a way that still feels like Ruby (i.e. via value manipulation through method calls rather than classic flow control blocks).
In simplest terms:
# "Only If" Logic:
final_value = original_value
final_value = different_value unless original_value == a_good_thing
# ... or ...
final_value = if original_value == a_good_thing
original_value
else
different_value
end
# ... becomes ...
final_value = original_value.only_if a_good_thing,
else: different_value
# "Unless" Logic:
final_value = original_value
final_value = different_value if original_value == a_bad_thing
# ... or ...
final_value = if original_value == a_bad_thing
different_value
else
original_value
end
# ... becomes ...
final_value = original_value.unless a_bad_thing,
then: different_value
# "Transform" Logic:
final_value = case original_value
when true then 'success'
when false then 'failure'
when String then %(error: "#{original_value}")
end
# ... becomes ...
final_value = original_value.transform true => 'success',
false => 'failure',
String => %(error: "#{original_value}")
Full documentation is available here, but do read below for a crash course on availble featues!
Installation
-
If your project uses Bundler:
- Add one of the following to your application's Gemfile:
# For on-demand usage: gem 'inline_transforms' - And then run a:
$ bundle install
- Add one of the following to your application's Gemfile:
-
Or, you can keep things simple with a manual install:
$ gem install inline_transforms
Usage
Object#only_if
only_if lets you specify a "good" value and an else replacement.
- If your
elsereplacement is aProc, it is resolved (viacall) before being returned.- If it has arity 0, it is
called with no params. - If it has arity 1, it is
called with the original value. - If it has arity -1 (a
lambdawith optional param), it iscalled with the original value.
- If it has arity 0, it is
- This method uses case comparison (
===), so you can check for range inclusion or class.
final = value.only_if good_value, else: fallback_value
# ... or ...
final = value.only_if good_value, else: -> { some_method_call with_params }
Object#unless
unless lets you specify a "bad" value and a then replacement.
- If your
thenreplacement is aProc, it is resolved (viacall) before being returned.- If it has arity 0, it is
called with no params. - If it has arity 1, it is
called with the original value. - If it has arity -1 (a
lambdawith optional param), it iscalled with the original value.
- If it has arity 0, it is
- This method uses case comparison (
===), so you can check for range inclusion or class.
final = value.unless bad_value, then: fallback_value
# ... or ...
final = value.unless bad_value, then: -> { some_method_call with_params }
Object#transform
transform lets you specify a transformation hash and will return the value for the first matching key, or (if no matching key is found) the :else value.
- Any
Procvalues in the transform hash are resolved (viacall) before being returned.- Values with arity 0 are
called with no params. - Values with arity 1 are
called with the original value. - Values with arity -1 (
lambdas with an optional param) arecalled with the original value.
- Values with arity 0 are
- This method uses case comparison (
===), so range and class keys work as you expect.
final = value.transform key_1 => value_1,
key_2 => value_2,
# ...
else: else_value
Potential Gotchas
-
Because
else:has special meaning,transformcannot check for the original value being the literalSymbol:else. -
Procinstances are only resolved if they need to be returned and are not the original value:value = -> { 'value proc' } replacement_proc = -> { 'replacement proc' } # Returns the original `value`, still a *Proc*. # # The original `value` is never resolved. # The `replacement_proc` is not resolved. # final = value.unless 99, then: replacement_proc # Returns the *String* 'replacement proc'. # # The original `value` is never resolved. # The `replacement_proc` DOES get resolved. # final = value.unless value, then: replacement_proc # Returns 99. # # The original `value` is never resolved. # The `replacement_proc` is not resolved. # final = value.transform value => 99, else: replacement_proc # Returns the *String* 'replacement proc'. # # The original `value` is never resolved. # The `replacement_proc` DOES get resolved. # final = value.transform 99 => 99, else: replacement_proc -
Because return
Procs are resolved before being passed back, you have to "proc-wrap" anyProcyou want returned as-is (😵💫):value = 'some value' replacement_proc = -> { 'replacement proc' } # Returns the `replacement_proc`, still a *Proc*. # value.unless 99, then: -> { replacement_proc }It should be exceedingly rare for someone to want to do this, but it is supported and this is how you would make that happen.
Contribution / Development
Bug reports and pull requests are welcome at: https://github.com/nestor-custodio/inline_transforms
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
Linting is courtesy of Rubocop (rake rubocop) and documentation is built using YARD. Please ensure you have a clean bill of health from Rubocop and that any new features and/or changes to behaviour are reflected in the adjacent documentation before submitting a pull request.
License
The inline_transforms gem is available as open source under the terms of the MIT License.