Class: Unexceptional::Result
- Inherits:
-
Object
- Object
- Unexceptional::Result
- Defined in:
- lib/unexceptional.rb
Class Method Summary collapse
-
.check(condition, error) ⇒ Object
Pass true or false and an error value.
-
.err(err) ⇒ Object
Returns a new ‘Result` respresenting failure.
-
.map_while(collection) ⇒ Object
Pass a block and a collection.
-
.ok(val = nil) ⇒ Object
Returns a new ‘Result` respresenting success.
-
.transaction ⇒ Object
Given a block, runs an ActiveRecord transaction.
-
.try(*procs) ⇒ Object
Tries to run a list of procs, aborting on the first failure, if any.
Instance Method Summary collapse
-
#and_then(next_result = nil) ⇒ Object
If this ‘Result` is an err, returns self:.
-
#err ⇒ Object
Returns the inner err value.
-
#err? ⇒ Boolean
Returns true if this Result is an err, false if this Result is ok.
-
#if_err {|self.err| ... } ⇒ Object
Yields this ‘Result` if this `Result` is an err.
-
#if_ok {|self.val| ... } ⇒ Object
Yields this ‘Result` if this Result is ok.
-
#initialize(ok, val, err) ⇒ Result
constructor
:nodoc:.
-
#ok? ⇒ Boolean
Returns true if this Result is ok, false if this Result is an err.
-
#unwrap ⇒ Object
(also: #ok)
Returns the inner success value.
Constructor Details
#initialize(ok, val, err) ⇒ Result
:nodoc:
227 228 229 230 231 |
# File 'lib/unexceptional.rb', line 227 def initialize(ok, val, err) # :nodoc: @ok = ok @val = val @err = err end |
Class Method Details
.check(condition, error) ⇒ Object
Pass true or false and an error value. If the first argument is ‘true`, returns an ok `Result`. If the first argument is false, returns an err `Result` wrapping the error value.
6 7 8 |
# File 'lib/unexceptional.rb', line 6 def self.check(condition, error) condition ? ok : err(error) end |
.err(err) ⇒ Object
Returns a new ‘Result` respresenting failure. Accepts an optional error value.
11 12 13 |
# File 'lib/unexceptional.rb', line 11 def self.err(err) new false, nil, err end |
.map_while(collection) ⇒ Object
Pass a block and a collection. The block must accept a member of the collection and return a ‘Result`.
If all members succeed, returns a ‘Result` wrapping all the mapped members:
Result.map([1, 2]) do |i|
Result.ok i * 2
end
# => Result.ok([1, 2])
Aborts on the first failure:
Result.map([1, 2, 3]) do |i|
if i == 2
Result.err '2 is invalid'
elsif i == 3
raise 'This is never executed because Result.map aborts on the previous element.'
else
Result.ok i * 2
end
end
# => Result.err('2 is invalid')
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/unexceptional.rb', line 37 def self.map_while(collection) Result.ok( collection.map do |member| result = yield member if result.err? return result else result.unwrap end end ) end |
.ok(val = nil) ⇒ Object
Returns a new ‘Result` respresenting success. Accepts an optional result value.
51 52 53 |
# File 'lib/unexceptional.rb', line 51 def self.ok(val = nil) new true, val, nil end |
.transaction ⇒ Object
Given a block, runs an ActiveRecord transaction. The block must return a ‘Result`. If the `Result` is an error, rolls back the transaction. Either way, returns the `Result`. You must call `require ’active_record’‘ before you call this method.
58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/unexceptional.rb', line 58 def self.transaction unless defined?(ActiveRecord) raise 'ActiveRecord is not defined' end result = nil ActiveRecord::Base.transaction do result = yield if result.err? raise ActiveRecord::Rollback end end result end |
.try(*procs) ⇒ Object
Tries to run a list of procs, aborting on the first failure, if any. Each proc must return a ‘Result`–either ok or err. Aborts on the first err, if any, returning the failed `Result`. If all procs return ok, returns the last `Result`.
Result.try(
-> { Result.ok 2 },
->(i) { Result.ok 3 * i }
)
# => Result.ok(6)
Result.try(
-> { Result.ok 2 },
->(_) { Result.err :uh_oh },
->(i) { Result.ok 3 * i }
)
# => Result.err(:uh_oh)
You can also pass tuples through and pattern-match:
Result.try(
-> { Result.ok [1, 2] },
->((a, b)) { Result.ok a + b }
)
# => Result.ok(3)
If you need to initialize a lot of objects along the way, passing them through the various procs via pattern-matching can be unwieldy. In that case, you can use the ‘#set` method along with instance variables:
Result.try(
-> { set :a, Result.ok(2) },
-> { set :b, Result.ok(@a * 3) },
-> { Result.ok(@b * 4) }
)
# => Result.ok(24)
This defines ‘#set` on whatever object is currently `self`. If `#set` was previously defined, it’ll be temporarily overwritten.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/unexceptional.rb', line 110 def self.try(*procs) if procs.empty? raise 'Must past at least one proc to Result.try' end procs.inject(nil) do |last_result, proc| # Ruby 2.2 introduced Binding#receiver. But to support Ruby <= 2.1, we use eval. ctx = proc.binding.eval('self') # Extend ctx's metaclass with the #set method, saving the previous #set if any. class << ctx if method_defined? :set alias_method :__set_before_try, :set end def set(var, result) if result.ok? instance_variable_set '@' + var.to_s, result.unwrap end result end end # Maybe call the proc, maybe with arguments. if last_result.nil? result = proc.call elsif !last_result.is_a?(Result) raise "Each proc in Result.try must return a Result, but proc returned #{last_result.inspect}" elsif last_result.ok? if proc.parameters.length == 0 result = proc.call else result = proc.call last_result.unwrap end else result = last_result end # Undo the changes to ctx's metaclass. class << ctx if method_defined? :__set_before_try alias_method :set, :__set_before_try else remove_method :set end end # Return the result of the current proc. result end end |
Instance Method Details
#and_then(next_result = nil) ⇒ Object
If this ‘Result` is an err, returns self:
Result
.err(:uh_oh)
.and_then { 'This block never executes' }
# => Result.err(:uh_oh)
If this ‘Result` is ok, then the behavior depends on what you passed to `and_then`:
# Passing a single argument:
Result
.ok('This value gets dropped')
.and_then(Result.ok('This is the final value'))
# => Result.ok('This is the final value')
# Passing a block:
Result
.ok(3)
.and_then { |v| v * 2 }
# => Result.ok(6)
# Passing nothing:
Result
.ok('This value gets dropped')
.and_then
# => Result.ok
187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/unexceptional.rb', line 187 def and_then(next_result = nil) if @ok if block_given? yield elsif next_result next_result else Result.ok end else self end end |
#err ⇒ Object
Returns the inner err value. Raises if this ‘Result` is ok.
214 215 216 217 218 219 220 |
# File 'lib/unexceptional.rb', line 214 def err if !@ok @err else raise "Called #err, but Result was ok." end end |
#err? ⇒ Boolean
Returns true if this Result is an err, false if this Result is ok.
223 224 225 |
# File 'lib/unexceptional.rb', line 223 def err? !@ok end |
#if_err {|self.err| ... } ⇒ Object
Yields this ‘Result` if this `Result` is an err.
202 203 204 205 |
# File 'lib/unexceptional.rb', line 202 def if_err yield self.err if !@ok self end |
#if_ok {|self.val| ... } ⇒ Object
Yields this ‘Result` if this Result is ok.
208 209 210 211 |
# File 'lib/unexceptional.rb', line 208 def if_ok yield self.val if @ok self end |
#ok? ⇒ Boolean
Returns true if this Result is ok, false if this Result is an err.
234 235 236 |
# File 'lib/unexceptional.rb', line 234 def ok? @ok end |
#unwrap ⇒ Object Also known as: ok
Returns the inner success value. Raises if this Result is an err.
239 240 241 242 243 244 245 |
# File 'lib/unexceptional.rb', line 239 def unwrap if @ok @val else raise "Called #unwrap on error: #{@err.inspect}" end end |