Confident.ruby
Be confident and narrative when writing code in ruby.
Gem contains useful abstractions for eliminating most condition and switch smells, treating anything just like a duck, implementing barricade and null object pattern efficiently.
Installation
Add this line to your application's Gemfile:
gem 'confident_ruby'
And then execute:
$ bundle
Or install it yourself as:
$ gem install confident_ruby
Usage
require "confident"
Eliminating condition
Not implemented
Given this code:
class Order
# ...
def charge
if date < SEASON_START || date > SEASON_END
quantity * normal_rate - discount
else
quantity * season_rate
end
end
# ...
end
Becomes:
class Order
# ...
def charge
Charge.lift(date).for(quantity)
end
# ...
end
Charge = Confident::Liftable.build do
lifts(to: :NormalCharge) { date < SEASON_START || date > SEASON_END }
lifts(to: :SeasonCharge) { true }
class NormalCharge < self
def for(quantity)
Money.lift(normal_rate) * quantity - Money.lift(discount)
end
end
class SeasonCharge < self
def for(quantity)
Money.lift(season_rate) * quantity
end
end
end
Eliminating switch
Not implemented
Given this code:
class Product
# ...
def cost
case kind
when "hosting"
hosting_cost
when "dedicated"
dedicated_cost
when "virtual"
virtual_cost
else
raise "Unreachable code"
end
end
# ...
end
becomes:
class Product
# ...
def cost
kind.cost
end
def kind
ProductKind.lift(@kind)
end
# ...
end
# could be abstracted even better, if the whole thing was in database source
ProductKind = Confident::Liftable.build do
lift_map "hosting" => :HostingProductKind,
"dedicated" => :DedicatedProductKind,
"virtual" => :VirtualProductKind
def cost
Confident::Result.error("Unknown product kind")
end
class HostingProductKind < self
def cost
Confident::Result.ok(Money.lift(hosting_kind))
end
end
class DedicatedProductKind < self
def cost
Confident::Result.ok(Money.lift(dedicated_kind))
end
end
class VirtualProductKind < self
def cost
Confident::Result.ok(Money.lift(virtual_kind))
end
end
end
Looks like we expanding our code and making it bigger at no time, but it is actually beneficial to us, because we don't need to duplicate knowledge on how to determine kind of product throughout a codebase, we just call Product.lift(kind_string_value), and we are done with it. You can implement this stuff in raw ruby easily, but this library wants to cut of a boilerplate code involved in this usually.
Handling success/failure result
Just use Confident::Result:
def some_computation_that_may_fail
Confident::Result.ok(compute)
rescue => e
Confident::Result.error(e.to_s)
end
puts some_computation_that_may_fail.on_error { |e| report_error(e) }.unwrap
If you have something that returns true in case of success and false in case of failure, you can use Confident::Result.from_condition(boolean_value, failure_message=nil):
Confident::Result.from_condition(some_weird_external_api, "Weird external API returned unexpected error")
Pure null object
Pure null object (Confident::NullObject) quacks to any method with itself.
If you have an input value that could be nil, you can just wrap it in Confident::AutoNullObject to get either original value or pure null object if it is nil.
Example usage with AutoNullObject:
def do_something_with(possibly_nil_value)
AutoNullObject(some_possibly_nil_value).say("hello")
end
do_something_with(fetch_me_a_value) # => "said: hello"
do_something_with(fetch_me_a_nil_value) # => #<Confident::NullObject:0x000001019f37f8>
TODO (to specify in this README)
- lift & bind
- null object with ability to define your own custom null object
- barricade
Contributing
- Fork it ( https://github.com/waterlink/confident.ruby/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request