Skip to content

blocknotes/eventish

Repository files navigation

Eventish

Gem Version Specs Rails 6 Specs Rails 7 Linters

Yet another events library with a simple API to handle... events 🎉

Main features:

  • composable: just require the components that you need;
  • with adapters: support ActiveSupport::Notifications and Wisper for pub/sub events;
  • with async events: support ActiveJob for events background execution;
  • with callbacks wrapper: support ActiveRecord callbacks;
  • with plugins: a simple logger and a Rails logger are included;
  • with conditional event execution (overriding callable?).

This component can be useful to:

  • decouple callbacks from the side-effect;
  • permit to test the side-effects in isolation;
  • permit to spy/block the side-effects;
  • permit to execute the side-effects from other contexts.

Please ⭐ if you like it.

Do you want to speak by events? Try eventish 😄

Install

  • Add to your Gemfile: gem 'eventish' (and execute bundle)
  • Create an initializer - ex. config/initializers/eventish.rb:
require 'eventish/adapters/active_support'

Eventish.setup do |config|
  config.adapter = Eventish::Adapters::ActiveSupport
end

Rails.configuration.to_prepare do
  # NOTE: required to load the event descendants when eager_load is off
  unless Rails.configuration.eager_load
    events = Rails.root.join('app/events/**/*.rb').to_s
    Dir[events].sort.each { |event| require event }
  end
end

Rails.configuration.after_initialize do
  Eventish::SimpleEvent.subscribe_all # NOTE: events will be available after this point

  Eventish.publish('app_loaded') # just a test event
end
  • Create some events - ex. app/events/main/app_loaded_event.rb:
module Main
  class AppLoadedEvent < Eventish::SimpleEvent
    def call(_none, _options = {})
      puts '> App loaded event'
    end
  end
end

For a complete example please take a look at the dummy app in the specs.

Adapters

Used for events subscription and publishing.

ActiveSupport Notification:

# initializer setup
require 'eventish/adapters/active_support'

Eventish.setup do |config|
  config.adapter = Eventish::Adapters::ActiveSupport
end

Wisper (NOTE: around callback wrappers are not supported):

# initializer setup
require 'wisper'
require 'eventish/adapters/wisper'

Eventish.setup do |config|
  config.adapter = Eventish::Adapters::Wisper
end

Simple events

Generic events not related to a specific component.

# initializer setup
Rails.configuration.after_initialize do
  # Subscribe all Eventish::SimpleEvent descendants using the configured adapter
  # The descendants event classes must be loaded before this point - see eager_load notes in the Install section
  Eventish::SimpleEvent.subscribe_all
end

Sample event - ex. app/events/main/test_event.rb:

module Main
  class TestEvent < Eventish::SimpleEvent
    def call(_none, _options = {})
      puts '> A test event'
    end

    class << self
      def event_name
        # this is optional, if not set the class name to string will be used
        'some_event'
      end
    end
  end
end

Publish the event with:

Eventish.publish('some_event')

Async events

Events executed in a background process. Only ActiveJob is supported for now.

# initializer setup
require 'active_job/railtie'
require 'eventish/active_job_event'

Rails.configuration.after_initialize do
  Eventish::ActiveJobEvent.subscribe_all
end

Sample event - ex. app/events/notifications/user_after_save_commit_event.rb:

module Notifications
  class UserAfterCommitEvent < Eventish::ActiveJobEvent
    def call(user, _options = {})
      Rails.logger.info ">>> User ##{user.id} after commit notification"
    end
  end
end

Callbacks

Wrappers for ActiveRecord callbacks using the postfix _event (ex. after_commit_event SomeEvent).

# initializer setup
require 'eventish/active_record/callback'
# sample model
class SomeModel < ActiveRecord::Base
  extend ::Eventish::ActiveRecord::Callback

  before_validation_event SomeBeforeValidationEvent
end

The related callback will be setup by the wrapper and the specified event class will be invoked accordingly.

Plugins

A plugins system is available for custom processing, a logger and a Rails logger are included in the gem.

# initializer setup
require 'eventish/plugins/rails_logger' # without rails_ for a simple stdout logger

Eventish.setup do |config|
  config.before_event = [Eventish::Plugins::RailsLogger]
  config.after_event = [Eventish::Plugins::RailsLogger]
end

A sample plugin:

module Eventish::Plugins::RailsLogger
  class << self
    def call(target, _args, event:, hook: nil, &_block)
      Rails.logger.debug "EVENT: #{hook} #{event.class.event_name} on #{target.inspect}"
    end
  end
end

Plugins can also be configured for single events overriding before_event and after_event.

Conditional events

Optionally an event can override the callable? method to define preconditions on the execution of the call method.

Example:

class TestEvent < Eventish::SimpleEvent
  def callable?(target)
    target.ready?
  end

  def call(target, _options = {})
    puts '> A test event'
  end
end

Do you like it? Star it!

If you use this component just star it. A developer is more motivated to improve a project when there is some interest.

Or consider offering me a coffee, it's a small thing but it is greatly appreciated: about me.

Contributors

License

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