Class: Perfume::Promise

Inherits:
Service show all
Defined in:
lib/perfume/promise.rb

Overview

Public: Very often we’re in a situation that we have to catch and eventuall log some errors, or even more, solely catch errors for purpose of logging. Other times we must perform multiple actions in different places according to the results produces by our method or service. We’d normally have to pass these results around deppening our stack trace. Here comes this a bit twisted implementation of a promise. Maybe twisted is wrong description, it’s rather simplified. It’s not parallel, it’s simple to the bones. It just allows you to define three kinds of callbacks: before call, on success and on failure of course. Since it inherits from our own Service, it essentially is one with own logging and access to class level call method.

Here’s few interesting properties:

  1. Define your logic in #call! method. Yes call with bang.

    class FindWally < Perfume::Promise
      NotFoundWarning = Class.new(Warning)
    
      def call!
        Wally.latest_location.tap do |location|
          raise NotFoundWarning, "No idea where is Wally!" if location.nil?
        end
      end
    end
    
  2. Failures are dependent on the kind of error thrown. It means that the flow is directed by raised errors. Slow you might say, but benchmarking this shows that the overhead is negligible in real life apps. This also allows a nifty trick. Look at the example of ‘FindWally` above. Why there’s ‘NotFoundWarning` inhertiting from magical `Warning` class? The `Warning` is bundled into each new promise defined. This errors are not thrown like regular exceptions. Instead they’re handled and logged as warnings. This error will be passed to our ‘failure` callbacks as well. Check example:

    FindWally.call do |_location|
      raise StandardError, "Something went wrong!"
    end
    

    This one raises ‘StandardError`, while this one:

    find_wally = FindWally.new
    find_wally.failure { |err| puts err.message }
    find_wally.call { |_location| raise FindWally::Warning, "Uuups!" }
    

    Will log WARNING with “Uuups!” message.

  3. Block passed to call is added to on success callbacks:

    FindWally.call do |location|
      puts location
    end
    
    $ "Madrid"
    

Constant Summary collapse

Warning =
Class.new(Exception)

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Service

call

Methods included from Gallus::Logging

included, #log

Methods inherited from SuperObject

args, args_accessor, args_reader, args_writer, #defaults, #init, init_args

Constructor Details

#initializePromise

Returns a new instance of Promise.



64
65
66
67
68
69
70
# File 'lib/perfume/promise.rb', line 64

def initialize(*)
  super

  @__before     = []
  @__on_success = []
  @__on_failure = []
end

Class Method Details

.inherited(child) ⇒ Object



60
61
62
# File 'lib/perfume/promise.rb', line 60

def self.inherited(child)
  child.const_set(:Warning, Warning)
end

Instance Method Details

#before(&block) ⇒ Object



72
73
74
75
# File 'lib/perfume/promise.rb', line 72

def before(&block)
  @__before << block
  self
end

#call(&block) ⇒ Object

Pubic: Safely executes your logic defined in call!, taking care that all callbacks are properly called.



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/perfume/promise.rb', line 103

def call(&block)
  @__ok = false
  @__before.each(&:call)
  success_callbacks = @__on_success + [block]
  call!.tap { |result| success_callbacks.compact.each { |callback| callback.call(result) } }
  @__ok = true
  self
rescue Warning => err
  log.warn(err)
  @__on_failure.each { |callback| callback.call(err) }
  return nil
end

#call!Object

Public: Your logic goes here. Flow is broken by raising an exception of local Error class or child classes. Any return value will be passed to on-success callbacks.

Raises:

  • (NotImplementedError)


97
98
99
# File 'lib/perfume/promise.rb', line 97

def call!
  raise NotImplementedError
end

#failure(&block) ⇒ Object



82
83
84
85
# File 'lib/perfume/promise.rb', line 82

def failure(&block)
  @__on_failure << block
  self
end

#failure?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/perfume/promise.rb', line 91

def failure?
  !success?
end

#success(&block) ⇒ Object



77
78
79
80
# File 'lib/perfume/promise.rb', line 77

def success(&block)
  @__on_success << block
  self
end

#success?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/perfume/promise.rb', line 87

def success?
  @__ok
end