Class: ShopifyCLI::Result::Success

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

Overview

Implements a container for wrapping a success value. The main purpose of the container is to support further transformations of the result and centralize error handling should any of the subsequent transformations fail:

result = Result
  .new("{}")
  .then { |json| JSON.parse(json) }
  .tap do |result|
    result.success? # => true
    result.value # => {}
  .then { |data| data.fetch(:firstname) }
  .tap do |result|
    result.failure? # => true
    result.error # => KeyError
  end

‘Success` implements two transformation functions: `then` and `map`. The former makes no assumption regarding the return value of the transformation. The latter on the other hand expects the transformation to be successful. If this assumption is violated, program execution is interrupted and an error is raised. As the purpose of result objects is to guard against exactly that. This is generally a flaw and requires the code to either be hardened or to substitute the call to `map` with a call to `then`. `map` should only be used for transformations that cannot fail and when the caller wants to state exactly that fact.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value) ⇒ Success

initializes a new ‘Success` from an arbitrary value.



88
89
90
# File 'lib/shopify_cli/result.rb', line 88

def initialize(value)
  @value = value
end

Instance Attribute Details

#valueObject (readonly)

Returns the value of attribute value.



84
85
86
# File 'lib/shopify_cli/result.rb', line 84

def value
  @value
end

Instance Method Details

#errorObject

raises an ‘UnexpectedSuccess` as a `Success` does not carry an error value.

Raises:



110
111
112
# File 'lib/shopify_cli/result.rb', line 110

def error
  raise UnexpectedSuccess
end

#failure?Boolean

always returns false to indicate that this result represents a success.

Returns:

  • (Boolean)


102
103
104
# File 'lib/shopify_cli/result.rb', line 102

def failure?
  false
end

#map(&block) ⇒ Object

returns a new ‘Success` wrapping the result of the given block. The block is called with the current value. If the block raises an exception or returns a `Failure`, an exception is raised. `map` assumes any transformation to succeed. Transformations that are expected to fail under certain conditions should only be transformed using `then`:

Success
  .new(nil)
  .map { |n| n + 1 } # => raises NoMethodError

Therefore, map should only be used here if the previous success value is guaranteed to be a number or if the block handles nil cases properly:

Success
  .new(nil)
  .map { |n| (n || 0) + 1 }
  .value # => 1


133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/shopify_cli/result.rb', line 133

def map(&block)
  self.then(&block).tap do |result|
    return result if result.success?

    result.unwrap { |error| error }.tap do |error|
      case error
      when Exception
        raise error
      else
        raise UnexpectedFailure, error
      end
    end
  end
end

#rescueObject

is a no-op and simply returns itself. Only a ‘Failure` can be transformed using `rescue`.



180
181
182
# File 'lib/shopify_cli/result.rb', line 180

def rescue
  self
end

#success?Boolean

always returns true to indicate that this result represents a success.

Returns:

  • (Boolean)


95
96
97
# File 'lib/shopify_cli/result.rb', line 95

def success?
  true
end

#then(&block) ⇒ Object

returns a new result by wrapping the return value of the block. The block is invoked with the current success value. The result can either be a ‘Success` or a `Failure`. The former is the default. The latter occurs when executing the block either

  • raised an exception,

  • returned an instance of a subclass of ‘Exception`, or

  • returned a ‘Failure`.

The example below illustrates this behavior:

result = Success
  .new(1)
  .then { |n| n + 1 }
  .tap do |result|
    result.success? # => true
    result.value # => 2
  end

result.then { |n| n / 0 }.error # => ZeroDivisionError
result.then { RuntimeError.new }.error # => RuntimeError
result.then { Failure.new("Boom!") }.error # => "Boom!"


172
173
174
# File 'lib/shopify_cli/result.rb', line 172

def then(&block)
  Result.wrap(&block).call(@value)
end

#unwrap(*args, &block) ⇒ Object

returns the success value and ignores the fallback value that was either provided as a method argument or by passing a block. However, the caller is still required to specify a fallback value to ensure that in the event of a ‘Failure` program execution can continue in a controlled manner:

Success.new(1).unwrap(0) => 1

Raises:

  • (ArgumentError)


200
201
202
203
# File 'lib/shopify_cli/result.rb', line 200

def unwrap(*args, &block)
  raise ArgumentError, "expected either a fallback value or a block" unless (args.length == 1) ^ block
  @value
end

#unwrap!Object

returns the value this success represents



187
188
189
# File 'lib/shopify_cli/result.rb', line 187

def unwrap!
  value
end