Build status

WithEvents

A simple events system for Ruby apps which supports bi-directional SNS/SQS messaging.

Dependencies

  • Ruby >= 2.3.3
  • Rake >= 12.3.1
  • Activesupport >= 4.2.7
  • Sidekiq >= 3.5.3
  • Circuitry 3.2

Installation

Add this line to your application's Gemfile:

gem 'with_events'

And then execute:

$ bundle

Or install it yourself as:

$ gem install with_events

Configuration

Setting up a Rakefile

If you are going to use included rake tasks, add this to your Rakefile:

spec = Gem::Specification.find_by_name 'with_events'
load "#{spec.gem_dir}/lib/tasks/with_events/with_events_tasks.rake"

Setting up Circuitry

If you would like to use SNS/SQS subscribing/publishing features, you need to configure Circuitry gem. Just follow this instructions.

Usage

Basic Usage (in-app publish/subscribe)

This type of messaging does not require Rakefile or Circuitry configuration.

require 'with_events'

class MyHeroClass
  include WithEvents

  stream :my_lovely_stream do
    event :game_over,
          condition: :really_game_over?,
          callback: :call_me_if_game_over
  end

  def really_game_over?
    true
  end

  def call_me_if_game_over
    puts 'Game over'
  end
end

hero = MyHeroClass.new
hero.game_over! if hero.game_over?

There might be situations where you will have a lot of events which have pretty same configuration. To make life easier, you can use configure_all method, which will aply configuration for all events in the stream.

require 'with_events'

class MyHeroClass
  include WithEvents

  stream :my_lovely_stream do
    configure_all callback: :call_me_if_game_over

    event :event_one,
          condition: -> { true }

    event :event_one,
          condition: -> { false }
  end

  def really_game_over?
    true
  end

  def call_me_if_game_over
    puts 'Game over'
  end
end

hero = MyHeroClass.new
hero.event_one!
#=> Game over
hero.event_two!
#=> Game over

Using with daily/hourly rake triggers for batch processing

You may want to automate a bit the process of asking resources if they are ready to trigger events (by calling #*?). This can be easily done by using background: true with appearance: :daily # or hourly options.

appearance option sets by which rake task your event may be processed.

require 'with_events'

class MyHeroClass
  include WithEvents

  stream :my_lovely_stream do
    event :game_over,
          condition: :really_game_over?,
          callback: :call_me_if_game_over,
          background: true,
          appearance: :daily, # or :hourly
          batch: User.active.find_each # any Enumerable
  end

  def really_game_over?
    true
  end

  def call_me_if_game_over
    puts 'Game over'
  end
end

Schedule for hourly/daily execution

$ rake with_events:daily 
$ rake with_events:hourly 

"Third-party" subscriptions

It is also possible to subscribe to events not only by using a callback option:

WithEvents::Stream.find(:my_lovely_stream).on(:game_over) do
  # ...
end

NOTE that this will also subscribe you to SQS/SNS events.

Sending events to SNS/SQS

You may send messages to SNS/SQS by setting a topic option for the stream. In addition, you need to specify identifier option.

identifier option (symbol, Proc, Class) allows to identify incoming message and bind an id for outgoing ones.

require 'with_events'

class MyModel < ActiveRecord::Base
  include WithEvents

  stream :my_lovely_stream, topic: 'my-topic' do
    event :game_over,
          condition: :really_game_over?,
          callback: :call_me_if_game_over,
          identifier: :id, # symbol, Proc or Class
  end

  def really_game_over?
    true
  end

  def call_me_if_game_over
    puts 'Game over'
  end
end

Subscribing to SNS/SQS events

To subscribe to SNS/SQS events you need to specify topic and finder options.

The finder option represents invokable type which should return resource identified by identifier invokable by the sender.

NOTE that subscriber will take the process. Run it in a separate process

require 'with_events'

class MyClass
  include WithEvents

  stream :my_lovely_stream, topic: 'my-topic' do
    event :game_over,
          condition: :really_game_over?,
          callback: :call_me_if_game_over,
          finder: ->(message) { SomeModel.find(message.id) }
  end

  def really_game_over?
    true
  end

  def call_me_if_game_over
    puts 'Game over'
  end
end

WithEvents::Stream.subscribe # NOTE this line

Supported invokable types

  • Proc
  • Symbol
  • Class

You may use them for condition, callback, identifier or finder options.

class CallbackClass
  def call(resource, *arguments)
    puts 'Game over'
  end
end

class MyHeroClass
  include WithEvents

  stream :my_lovely_stream do
    event :game_over,
          condition: :really_game_over?,
          callback: CallbackClass

    event :you_won,
          condition: -> { really_won? },
          callback: CallbackClass
  end

  def really_won?
      false
    end

  def really_game_over?
    true
  end
end

Contributing

If you are going to contribute to this repo, please follow these simple rules:

  • Cover you wrote with specs
  • Check you wrote with Rubocop
  • Use Karma-style comit messages

License

The gem is available as open source under the terms of the MIT License.