Skip to content

Commit

Permalink
Merge pull request #4 from Kaligo/feature/event-source
Browse files Browse the repository at this point in the history
[Feature] Add EventSource mixin
  • Loading branch information
Drenmi authored Mar 9, 2021
2 parents 66ce693 + 2394fc2 commit 989aa76
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ AllCops:
- "Rakefile"
SuggestExtensions: false

Lint/EmptyBlock:
Enabled: false

Metrics/BlockLength:
Exclude:
- "spec/**/*"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.5.0

### New features

- Add `EventSource` mixin.

## 0.4.0

### New features
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
stimpack (0.4.0)
stimpack (0.5.0)
activesupport (~> 6.1)

GEM
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,47 @@ and behaviour.

## Table of Contents

- [EventSource](#eventsource)
- [FunctionalObject](#functionalobject)
- [OptionsDeclaration](#optionsdeclaration)
- [ResultMonad](#resultmonad)

## EventSource

A mixin that turns the class into an event emitter with which others can
register listeners. The class can then use `#emit` to broadcast events to any
listensers.

**Example:**

Given the following event source:

```ruby
class Foo
include Stimpack::EventSource

def bar
emit(:bar, { message: "Hello, world!" })
end
end
```

we can register a callback to listen for events from another part of our
application, and we will receive an event object when the event is emitted:

```ruby
Foo.on(:bar) do |event|
puts event.message
end

Foo.new.bar
#=> "Hello, world!"
```

*Note: Callbacks are invoked synchronously in the same thread, so don't use
this to perform long-running tasks. You can use the event listener to schedule
a background job, though!*

## FunctionalObject

A simple mixin that provides a shorthand notation for instantiating and
Expand Down
58 changes: 58 additions & 0 deletions lib/stimpack/event_source.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

# TODO: Remove dependency on ActiveSupport.
#
require "active_support/core_ext/class/attribute"

module Stimpack
module EventSource
class Event
def initialize(name, data = {})
@name = name
@data = data
end

attr_reader :name, :data

def respond_to_missing?(method)
data.key?(method) || super
end

def method_missing(method, *arguments, &block)
if data.key?(method)
data[method]
else
super
end
end
end

module ClassMethods
def self.extended(klass)
klass.class_eval do
# TODO: Remove dependency on ActiveSupport.
#
class_attribute :event_listeners,
instance_accessor: false,
default: Hash.new { |h, k| h[k] = [] }
end
end

def on(event_name, &block)
event_listeners["#{self}.#{event_name}"] << block
end
end

def self.included(klass)
klass.extend(ClassMethods)
end

def emit(event_name, data)
event_name = "#{self.class}.#{event_name}"

event = Event.new(event_name, data)

self.class.event_listeners[event_name].each { |l| l.(event) }
end
end
end
2 changes: 1 addition & 1 deletion lib/stimpack/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Stimpack
VERSION = "0.4.0"
VERSION = "0.5.0"
end
36 changes: 36 additions & 0 deletions spec/stimpack/event_source/event_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require "stimpack/event_source"

RSpec.describe Stimpack::EventSource::Event do
subject(:event) { described_class.new(name, data) }

describe "#name" do
let(:name) { "foo.bar.baz" }
let(:data) {}

it { expect(event.name).to eq("foo.bar.baz") }
end

describe "#method_missing" do
let(:name) { "foo.bar.baz" }

context "when field is found in data" do
let(:data) do
{
foo: "bar"
}
end

it { expect(event.foo).to eq("bar") }
end

context "when field is not found in data" do
let(:data) do
{}
end

it { expect { event.foo }.to raise_error(NoMethodError) }
end
end
end
35 changes: 35 additions & 0 deletions spec/stimpack/event_source_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "stimpack/event_source"

RSpec.describe Stimpack::EventSource do
subject(:service) { klass }

let(:klass) do
Class.new do
include Stimpack::EventSource

def self.to_s
"Foo"
end
end
end

describe ".on" do
it { expect { service.on(:foo) {} }.to change { klass.event_listeners["Foo.foo"].size }.by(1) }
end

describe "#emit" do
let(:receiver) { spy }

before do
allow(receiver).to receive(:qux)

service.on(:qux) { |d| receiver.baz(d) }

service.new.emit(:qux, { quux: 1 })
end

it { expect(receiver).to have_received(:baz).with(Stimpack::EventSource::Event) }
end
end

0 comments on commit 989aa76

Please sign in to comment.