Informator

Gem Version Build Status Dependency Status Code Climate Coverage Inline docs

The wisper-inspired tiny implementation of publish/subscribe design pattern.

The implementation differs from the original wisper's approach in the following aspects:

  • Unlike Wisper::Publisher that calls listener methods depending on the event, the Informator uses the same listener's callback for sending all events. Instead it wraps attributes to Informator::Event container and uses its as the argument for the callback.

  • The Informator has two separate methods - #remember and #publish. The first one is used to build and collect events, while the second one publishes all unpublished events at once (and clears the list of events to be published).

  • The Informator also defines #publish! method that is the same as #publish except for it throws the :published, that should be catched somewhere. This is just a shortcut for exiting from a use cases.

  • The #publish method returns the list of published events. The exception thrown by #publish! carries that list as well. This allows not only to publish them to listeners but to return them to caller. This is necessary to simplify chaining of service objects that includes the Informator.

  • The Informator has no global neither async subscribers. The gem's scope is narrower. Its main goal is to support service objects, that are either chained to each other, or publishes their results to decoupled listeners.

Synopsis

The Informator module API defines 4 instance methods:

  • #subscribe to subscribe listeners for receiving events, published by the informator
  • #remember to build an event and keep it unpublished
  • #publish to publish all events keeped since last publishing
  • #publish! the same as #publish, except for it throws :published exception which carries a list of events

Except for the Informator the module defines public class Informator::Event for immutable events, that has 3 attributes:

  • #type for symbolic type of the event
  • #attributes for hash of attributes, carried by the event
  • #messages for array of human-readable messages, describing the event

The event instance is build by the #remember, #publish or #publish! methods and is sent to listeners by either #publish or #publish!. When sending an event, the informator just calls the listeners callback, defined by #subscribe method, and gives it a corresponding event object as the only argument.

require "informator"

class Listener
  def receive(event)
    puts "The listener received #{ event.type }: #{ event.messages }"
  end
end

listener = Listener.new

class Service
  include Informator

  def call
    catch(:published) { do_some_staff }
  end

  def do_some_staff
    # ...
    remember :success, "for now all is fine", foo: :bar
    #
    publish! :error, "OMG!", bar: :baz
  end
end

service = Service.new
service.subscribe listener, :receive
result = service.call
# The listener received success: ["for now all is fine"]
# The listener received error: ["OMG!"]
# => [
#      #<Informator::Event @type=:success @attributes={ foo: :bar } @messages=["for now all is fine"]>,
#      #<Informator::Event @type=:error @attributes={ bar: :baz } @messages=["OMG!"]>
#    ]

In the example above the listener#receive method is called twice with the first, and then with the second event as an argument.

Then the publish! throws an exception that carries the list of messages. The service#call catches it and returns the array of events. With this feature it is possible not only to subscribe for the service's events but receive it directly, combining both telling and asking when necessary.

Installation

Add this line to your application's Gemfile:

# Gemfile
gem "informator"

Then execute:

bundle

Or add it manually:

gem install informator

Compatibility

Tested under rubies compatible to MRI 2.1+.

Uses RSpec 3.0+ for testing and hexx-suit for dev/test tools collection.

Contributing

  • Read the STYLEGUIDE
  • Fork the project
  • Create your feature branch (git checkout -b my-new-feature)
  • Add tests for it
  • Commit your changes (git commit -am '[UPDATE] Add some feature')
  • Push to the branch (git push origin my-new-feature)
  • Create a new Pull Request

License

See the MIT LICENSE.