Resultr
Ruby beautiful results.
Resultr provides a simple interface to work with computation results, highly inspired by Rust results for handling errors on function returns.
Installation
Add Resultr to your Gemfile:
gem 'resultr'and run bundle install from your shell.
Getting Started
Good and bad results
Resultr works with two kinds of results, good results (Resultr.ok) and bad
results (Resultr.err). You can store any kind of data on any kind of result.
good_result = Resultr.ok(42)
# => <Resultr::Result @kind=:ok @value=42>
good_result.ok?
# => true
good_result.value
# => 42
bad_result = Resultr.err('foo')
# => <Resultr::Result @kind=:err @value="foo">
bad_result.err?
# => true
bad_result.reason
# => "foo"#reason is an alias of #value, but is a common practice
to use #value for good results and #reason for bad results.
Chaining results
You can chain results with #and_then and #or_else methods,
both will receive a block using result's value / reason and returns
the block return or itself, depending on result kind.
def shout(word)
if word == 'marco'
Resultr.ok 'polo'
else
Resultr.err 'unknown word'
end
end
# =========================================
# Using #and_then to chaining good results.
# =========================================
shout('marco').and_then { |value| "#{value}!" }
# => "polo!"
shout('foo').and_then { |value| "#{value}!" }
# => <Resultr::Result @kind=:err @value="unknown word">
# =======================================
# Using #or_else to chaining bad results.
# =======================================
shout('marco').or_else { |reason| "#{reason}, try 'marco'" }
# => <Resultr::Result @kind=:ok @value="polo">
shout('foo').or_else { |reason| "#{reason}, try 'marco'" }
# => "unknown word, try 'marco'"
# ================================================
# Using both to chaining the two kinds of results.
# ================================================
shout('marco').and_then { |v| Resultr.ok "#{v}!" }.or_else { |r| Resultr.err "#{r}, try 'marco'" }
# => <Resultr::Result @kind=:ok @value="polo!">
shout('foo').and_then { |v| Resultr.ok "#{v}!" }.or_else { |r| Resultr.err "#{r}, try 'marco'" }
# => <Resultr::Result @kind=:err @value="unknown word, try 'marco'">Elegant result branching
Resultr provides a sugar syntax for branching results by kind,
its called #thus and works like a flavor of case statement.
def get_posts
response = get('/posts')
if response.status == 200
Resultr.ok response.body
else
Resultr.err response.body
end
end
# ==================================
# Using #thus for branching actions.
# ==================================
get_posts.thus do |result|
result.ok do |value|
render json: { posts: value }
end
result.err do |reason|
render json: { error: reason }
end
end
# => ["{\"posts\":[{\"title\":\"The Free Lunch Is Over\"}]}"]
# =====================================
# Using #thus for branching assignment.
# =====================================
posts = get_posts.thus do |result|
result.ok # if you omit the block, it returns value
result.err { |_reason| [{ title: 'The Placeholder Post' }] }
end
# => [{ title: "The Free Lunch Is Over" }]Raising exceptions for bad results
If you don't want to handle possible result errors, you can use
#expect!, that returns the result value when successful or raises
an exception with the error reason, you can also provides a custom
message for the exception.
def write_on(file, text)
if File.exist?(file)
File.open(file, 'w') { |f| f.write(text) }
Resultr.ok(text)
else
Resultr.err('File not found')
end
end
write_on('diary.txt', 'Dear diary').expect!
# => "Dear diary"
write_on('wrong.txt', 'Dear diary').expect!
# => Resultr::ExpectationError: File not found
write_on('wrong.txt', 'Dear diary').expect!('Failed to write text')
# => Resultr::ExpectationError: Failed to write textLicense
Resultr is freely distributable under the MIT license