Class: Unexceptional::Result

Inherits:
Object
  • Object
show all
Defined in:
lib/unexceptional.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ok, val, err) ⇒ Result

:nodoc:



195
196
197
198
199
# File 'lib/unexceptional.rb', line 195

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

.transactionObject

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(
  -> { @a = Result.ok 2 },
  -> { @b = Result.ok @a * 3 },
  -> { Result.ok(@b * 4) }
)
# => Result.ok(24)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/unexceptional.rb', line 107

def self.try(*procs)
  if procs.empty?
    raise 'Must past at least one proc to Result.try'
  end
  ctx = TryContext.new
  procs.inject(nil) do |last_result, proc|
    if last_result.nil?
      ctx.instance_exec(&proc)
    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
        ctx.instance_exec(&proc)
      else
        ctx.instance_exec(last_result.unwrap, &proc)
      end
    else
      last_result
    end
  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


155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/unexceptional.rb', line 155

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

#errObject

Returns the inner err value. Raises if this ‘Result` is ok.



182
183
184
185
186
187
188
# File 'lib/unexceptional.rb', line 182

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.

Returns:

  • (Boolean)


191
192
193
# File 'lib/unexceptional.rb', line 191

def err?
  !@ok
end

#if_err {|self.err| ... } ⇒ Object

Yields this ‘Result` if this `Result` is an err.

Yields:



170
171
172
173
# File 'lib/unexceptional.rb', line 170

def if_err
  yield self.err if !@ok
  self
end

#if_ok {|self.val| ... } ⇒ Object

Yields this ‘Result` if this Result is ok.

Yields:

  • (self.val)


176
177
178
179
# File 'lib/unexceptional.rb', line 176

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.

Returns:

  • (Boolean)


202
203
204
# File 'lib/unexceptional.rb', line 202

def ok?
  @ok
end

#unwrapObject Also known as: ok

Returns the inner success value. Raises if this Result is an err.



207
208
209
210
211
212
213
# File 'lib/unexceptional.rb', line 207

def unwrap
  if @ok
    @val
  else
    raise "Called #unwrap on error: #{@err.inspect}"
  end
end