octiron 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +31 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.rubocop.yml +78 -0
- data/.travis.yml +11 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +70 -0
- data/LICENSE +30 -0
- data/README.md +159 -0
- data/Rakefile +25 -0
- data/lib/octiron.rb +11 -0
- data/lib/octiron/events/bus.rb +161 -0
- data/lib/octiron/support/camel_case.rb +27 -0
- data/lib/octiron/support/constantize.rb +43 -0
- data/lib/octiron/support/identifiers.rb +45 -0
- data/lib/octiron/transmogrifiers/registry.rb +201 -0
- data/lib/octiron/version.rb +12 -0
- data/lib/octiron/world.rb +94 -0
- data/octiron.gemspec +50 -0
- data/spec/events_bus_spec.rb +322 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support_camel_case_spec.rb +24 -0
- data/spec/support_constantize_spec.rb +64 -0
- data/spec/support_identifiers_spec.rb +70 -0
- data/spec/transmogrifiers_registry_spec.rb +311 -0
- data/spec/world_spec.rb +58 -0
- metadata +191 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# octiron
|
4
|
+
# https://github.com/jfinkhaeuser/octiron
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other octiron contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
module Octiron
|
10
|
+
# The current release version
|
11
|
+
VERSION = "0.1.0".freeze
|
12
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# octiron
|
4
|
+
# https://github.com/jfinkhaeuser/octiron
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other octiron contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'octiron/events/bus'
|
11
|
+
require 'octiron/transmogrifiers/registry'
|
12
|
+
|
13
|
+
##
|
14
|
+
# World module, putting together the event bus and transmogrifier registry with
|
15
|
+
# easy access functions.
|
16
|
+
module Octiron::World
|
17
|
+
##
|
18
|
+
# Modules also can have class methods
|
19
|
+
module ClassMethods
|
20
|
+
##
|
21
|
+
# Singleton transmogrifier registry
|
22
|
+
# @return (Octiron::Transmogrifiers::Registry) the registry.
|
23
|
+
def transmogrifier_registry
|
24
|
+
if @transmogrifier_registry.nil?
|
25
|
+
@transmogrifier_registry = ::Octiron::Transmogrifiers::Registry.new
|
26
|
+
end
|
27
|
+
return @transmogrifier_registry
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Singleton event bus
|
32
|
+
# @return (Octiron::Events::Bus) the bus.
|
33
|
+
def event_bus
|
34
|
+
if @event_bus.nil?
|
35
|
+
@event_bus = ::Octiron::Events::Bus.new
|
36
|
+
end
|
37
|
+
return @event_bus
|
38
|
+
end
|
39
|
+
end # module ClassMethods
|
40
|
+
extend ClassMethods
|
41
|
+
|
42
|
+
##
|
43
|
+
# Delegator for the on_transmogrify(FROM).to TO syntax
|
44
|
+
# @api private
|
45
|
+
class TransmogrifierRegistrator
|
46
|
+
def initialize(from)
|
47
|
+
@from = from
|
48
|
+
end
|
49
|
+
|
50
|
+
def to(to, transmogrifier_object = nil, &transmogrifier_proc)
|
51
|
+
::Octiron::World.transmogrifier_registry.register(@from, to, false,
|
52
|
+
transmogrifier_object,
|
53
|
+
&transmogrifier_proc)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Delegator for the transmogrify(FROM).to TO syntax
|
59
|
+
# @api private
|
60
|
+
class TransmogrifyDelegator
|
61
|
+
def initialize(from)
|
62
|
+
@from = from
|
63
|
+
end
|
64
|
+
|
65
|
+
def to(to)
|
66
|
+
return ::Octiron::World.transmogrifier_registry.transmogrify(@from, to)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Register a transmogrifier with the singleton transmogrifier registry
|
72
|
+
def on_transmogrify(from)
|
73
|
+
return TransmogrifierRegistrator.new(from)
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Transmogrify using the singleton transmogrifier registry
|
78
|
+
def transmogrify(from)
|
79
|
+
return TransmogrifyDelegator.new(from)
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Subscribe an event handler to an event with the singleton event bus
|
84
|
+
def on_event(event_id, handler_object = nil, &handler_proc)
|
85
|
+
::Octiron::World.event_bus.subscribe(event_id, handler_object, &handler_proc)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Publish an event on the singleton event bus
|
90
|
+
def publish(event)
|
91
|
+
::Octiron::World.event_bus.publish(event)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/octiron.gemspec
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# octiron
|
4
|
+
# https://github.com/jfinkhaeuser/octiron
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other octiron contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
|
10
|
+
lib = File.expand_path('../lib', __FILE__)
|
11
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
12
|
+
require 'octiron/version'
|
13
|
+
|
14
|
+
# rubocop:disable Style/UnneededPercentQ, Style/ExtraSpacing
|
15
|
+
# rubocop:disable Style/SpaceAroundOperators, Metrics/BlockLength
|
16
|
+
Gem::Specification.new do |spec|
|
17
|
+
spec.name = "octiron"
|
18
|
+
spec.version = Octiron::VERSION
|
19
|
+
spec.authors = ["Jens Finkhaeuser"]
|
20
|
+
spec.email = ["jens@finkhaeuser.de"]
|
21
|
+
spec.description = %q(
|
22
|
+
Events octiron responds to can be any classes or Hash prototypes. Using
|
23
|
+
transmogrifiers, events can be turned into other kinds of events
|
24
|
+
transparently.
|
25
|
+
)
|
26
|
+
spec.summary = %q(
|
27
|
+
Octiron is an event bus with the ability to magically transform events.
|
28
|
+
)
|
29
|
+
spec.homepage = "https://github.com/jfinkhaeuser/octiron"
|
30
|
+
spec.license = "MITNFA"
|
31
|
+
|
32
|
+
spec.files = `git ls-files -z`.split("\x0")
|
33
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
34
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
35
|
+
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
spec.required_ruby_version = '>= 2.0'
|
38
|
+
|
39
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
40
|
+
spec.add_development_dependency "rubocop", "~> 0.46"
|
41
|
+
spec.add_development_dependency "rake", "~> 11.3"
|
42
|
+
spec.add_development_dependency "rspec", "~> 3.5"
|
43
|
+
spec.add_development_dependency "simplecov", "~> 0.12"
|
44
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
45
|
+
|
46
|
+
spec.add_dependency "collapsium", "~> 0.8"
|
47
|
+
spec.add_dependency "rgl", "~> 0.5"
|
48
|
+
end
|
49
|
+
# rubocop:enable Style/SpaceAroundOperators, Metrics/BlockLength
|
50
|
+
# rubocop:enable Style/UnneededPercentQ, Style/ExtraSpacing
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/octiron/events/bus'
|
3
|
+
|
4
|
+
class TestEvent
|
5
|
+
end
|
6
|
+
|
7
|
+
module TestModule
|
8
|
+
class InnerTestEvent
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestHandler
|
13
|
+
attr_reader :invoked, :event
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@invoked = 0
|
17
|
+
@event = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(event)
|
21
|
+
@event = event
|
22
|
+
@invoked += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Octiron::Events::Bus do
|
27
|
+
describe "construction" do
|
28
|
+
it "can be constructed without a default namespace" do
|
29
|
+
bus = nil
|
30
|
+
expect do
|
31
|
+
bus = ::Octiron::Events::Bus.new
|
32
|
+
end.not_to raise_error
|
33
|
+
|
34
|
+
expect(bus.default_namespace).to eql 'Octiron::Events'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can be constructed with a namespace" do
|
38
|
+
bus = nil
|
39
|
+
expect do
|
40
|
+
bus = ::Octiron::Events::Bus.new(::Octiron::Events)
|
41
|
+
end.not_to raise_error
|
42
|
+
|
43
|
+
expect(bus.default_namespace).to eql 'Octiron::Events'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "subscription" do
|
48
|
+
before :each do
|
49
|
+
@bus = ::Octiron::Events::Bus.new(::Octiron::Events)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "requires a handler" do
|
53
|
+
expect do
|
54
|
+
@bus.subscribe(TestEvent)
|
55
|
+
end.to raise_error(ArgumentError)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can subscribe an object handler for an event" do
|
59
|
+
klass = nil
|
60
|
+
expect do
|
61
|
+
klass = @bus.subscribe(TestEvent, TestHandler.new)
|
62
|
+
end.not_to raise_error
|
63
|
+
expect(klass).to eql 'TestEvent'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "can subscribe a handler proc for an event" do
|
67
|
+
klass = nil
|
68
|
+
expect do
|
69
|
+
klass = @bus.subscribe(TestEvent) do |_|
|
70
|
+
end
|
71
|
+
end.not_to raise_error
|
72
|
+
expect(klass).to eql 'TestEvent'
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "event name validation" do
|
76
|
+
it "accepts string event IDs" do
|
77
|
+
klass = nil
|
78
|
+
expect do
|
79
|
+
klass = @bus.subscribe('TestEvent', TestHandler.new)
|
80
|
+
end.not_to raise_error
|
81
|
+
expect(klass).to eql 'TestEvent'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "accepts symbolized/underscored event IDs" do
|
85
|
+
klass = nil
|
86
|
+
expect do
|
87
|
+
klass = @bus.subscribe(:test_event, TestHandler.new)
|
88
|
+
end.not_to raise_error
|
89
|
+
expect(klass).to eql 'TestEvent'
|
90
|
+
|
91
|
+
expect do
|
92
|
+
@bus.subscribe(:inner_test_event, TestHandler.new)
|
93
|
+
end.to raise_error(NameError)
|
94
|
+
|
95
|
+
bus = ::Octiron::Events::Bus.new(::TestModule)
|
96
|
+
expect do
|
97
|
+
klass = bus.subscribe(:inner_test_event, TestHandler.new)
|
98
|
+
end.not_to raise_error
|
99
|
+
expect(klass).to eql 'TestModule::InnerTestEvent'
|
100
|
+
end
|
101
|
+
|
102
|
+
it "accepts Hash event IDs" do
|
103
|
+
proto = {
|
104
|
+
a: nil,
|
105
|
+
b: {
|
106
|
+
c: 42,
|
107
|
+
},
|
108
|
+
}
|
109
|
+
|
110
|
+
klass = nil
|
111
|
+
expect do
|
112
|
+
klass = @bus.subscribe(proto, TestHandler.new)
|
113
|
+
end.not_to raise_error
|
114
|
+
expect(klass).to eql proto
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "event notification" do
|
120
|
+
before :each do
|
121
|
+
@bus = ::Octiron::Events::Bus.new(::Octiron::Events)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "notifies a single handler object" do
|
125
|
+
event = TestEvent.new
|
126
|
+
handler = TestHandler.new
|
127
|
+
|
128
|
+
@bus.subscribe(TestEvent, handler)
|
129
|
+
@bus.publish(event)
|
130
|
+
|
131
|
+
expect(handler.invoked).to eql 1
|
132
|
+
expect(handler.event.object_id).to eql event.object_id
|
133
|
+
end
|
134
|
+
|
135
|
+
it "notifies multiple handler objects" do
|
136
|
+
event = TestEvent.new
|
137
|
+
handler1 = TestHandler.new
|
138
|
+
handler2 = TestHandler.new
|
139
|
+
|
140
|
+
@bus.subscribe(TestEvent, handler1)
|
141
|
+
@bus.subscribe(TestEvent, handler2)
|
142
|
+
@bus.publish(event)
|
143
|
+
|
144
|
+
expect(handler1.invoked).to eql 1
|
145
|
+
expect(handler1.event.object_id).to eql event.object_id
|
146
|
+
expect(handler2.invoked).to eql 1
|
147
|
+
expect(handler2.event.object_id).to eql event.object_id
|
148
|
+
end
|
149
|
+
|
150
|
+
it "notifies a single handler proc" do
|
151
|
+
event = TestEvent.new
|
152
|
+
got_event = nil
|
153
|
+
|
154
|
+
@bus.subscribe(TestEvent) do |fired|
|
155
|
+
got_event = fired
|
156
|
+
end
|
157
|
+
@bus.publish(event)
|
158
|
+
|
159
|
+
expect(got_event.object_id).to eql event.object_id
|
160
|
+
end
|
161
|
+
|
162
|
+
it "notifies multiple handler procs" do
|
163
|
+
event = TestEvent.new
|
164
|
+
got_event1 = nil
|
165
|
+
got_event2 = nil
|
166
|
+
|
167
|
+
@bus.subscribe(TestEvent) do |fired|
|
168
|
+
got_event1 = fired
|
169
|
+
end
|
170
|
+
@bus.subscribe(TestEvent) do |fired|
|
171
|
+
got_event2 = fired
|
172
|
+
end
|
173
|
+
@bus.publish(event)
|
174
|
+
|
175
|
+
expect(got_event1.object_id).to eql event.object_id
|
176
|
+
expect(got_event2.object_id).to eql event.object_id
|
177
|
+
end
|
178
|
+
|
179
|
+
it "notifies from a Hash event" do
|
180
|
+
proto = {
|
181
|
+
a: nil,
|
182
|
+
b: {
|
183
|
+
c: 42,
|
184
|
+
},
|
185
|
+
}
|
186
|
+
handler = TestHandler.new
|
187
|
+
|
188
|
+
@bus.subscribe(proto, handler)
|
189
|
+
|
190
|
+
# Bad event #1
|
191
|
+
bad_event1 = {
|
192
|
+
a: 'foo',
|
193
|
+
# :b is missing
|
194
|
+
}
|
195
|
+
@bus.publish(bad_event1)
|
196
|
+
expect(handler.invoked).to eql 0
|
197
|
+
|
198
|
+
# Bad event #2
|
199
|
+
bad_event2 = {
|
200
|
+
a: 'foo',
|
201
|
+
b: {
|
202
|
+
c: 123, # value mismatch
|
203
|
+
}
|
204
|
+
}
|
205
|
+
@bus.publish(bad_event2)
|
206
|
+
expect(handler.invoked).to eql 0
|
207
|
+
|
208
|
+
# Good event
|
209
|
+
good_event = {
|
210
|
+
a: 'foo',
|
211
|
+
b: {
|
212
|
+
c: 42,
|
213
|
+
}
|
214
|
+
}
|
215
|
+
@bus.publish(good_event)
|
216
|
+
expect(handler.invoked).to eql 1
|
217
|
+
end
|
218
|
+
|
219
|
+
it "matches multiple Hash prototypes" do
|
220
|
+
# Two similar prototypes
|
221
|
+
proto1 = {
|
222
|
+
a: 42,
|
223
|
+
}
|
224
|
+
handler1 = TestHandler.new
|
225
|
+
@bus.subscribe(proto1, handler1)
|
226
|
+
|
227
|
+
proto2 = {
|
228
|
+
a: nil,
|
229
|
+
}
|
230
|
+
handler2 = TestHandler.new
|
231
|
+
@bus.subscribe(proto2, handler2)
|
232
|
+
|
233
|
+
# Fire one event that matches both
|
234
|
+
@bus.publish(a: 42)
|
235
|
+
|
236
|
+
expect(handler1.invoked).to eql 1
|
237
|
+
expect(handler2.invoked).to eql 1
|
238
|
+
end
|
239
|
+
|
240
|
+
it "stores handlers for the best matching prototype" do
|
241
|
+
# Two different prototypes
|
242
|
+
proto1 = {
|
243
|
+
b: 42,
|
244
|
+
}
|
245
|
+
handler1 = TestHandler.new
|
246
|
+
@bus.subscribe(proto1, handler1)
|
247
|
+
|
248
|
+
proto2 = {
|
249
|
+
a: nil,
|
250
|
+
}
|
251
|
+
handler2 = TestHandler.new
|
252
|
+
@bus.subscribe(proto2, handler2)
|
253
|
+
|
254
|
+
handler3 = TestHandler.new
|
255
|
+
@bus.subscribe(proto2, handler3)
|
256
|
+
|
257
|
+
# Fire one event that matches one prototype
|
258
|
+
@bus.publish(a: 42)
|
259
|
+
|
260
|
+
expect(handler1.invoked).to eql 0
|
261
|
+
expect(handler2.invoked).to eql 1
|
262
|
+
expect(handler3.invoked).to eql 1
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "unsubscription" do
|
267
|
+
before :each do
|
268
|
+
@bus = ::Octiron::Events::Bus.new(::Octiron::Events)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "requires a handler" do
|
272
|
+
expect do
|
273
|
+
@bus.unsubscribe(TestEvent)
|
274
|
+
end.to raise_error(ArgumentError)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "unsubscripes object handlers properly" do
|
278
|
+
event = TestEvent.new
|
279
|
+
handler1 = TestHandler.new
|
280
|
+
handler2 = TestHandler.new
|
281
|
+
|
282
|
+
@bus.subscribe(TestEvent, handler1)
|
283
|
+
@bus.subscribe(TestEvent, handler2)
|
284
|
+
@bus.publish(event)
|
285
|
+
|
286
|
+
expect(handler1.invoked).to eql 1
|
287
|
+
expect(handler1.event.object_id).to eql event.object_id
|
288
|
+
expect(handler2.invoked).to eql 1
|
289
|
+
expect(handler2.event.object_id).to eql event.object_id
|
290
|
+
|
291
|
+
@bus.unsubscribe(TestEvent, handler2)
|
292
|
+
@bus.publish(event)
|
293
|
+
|
294
|
+
expect(handler1.invoked).to eql 2
|
295
|
+
expect(handler2.invoked).to eql 1
|
296
|
+
end
|
297
|
+
|
298
|
+
it "unsubscripes proc handlers properly" do
|
299
|
+
event = TestEvent.new
|
300
|
+
invoked1 = 0
|
301
|
+
invoked2 = 0
|
302
|
+
|
303
|
+
@bus.subscribe(TestEvent) do |_|
|
304
|
+
invoked1 += 1
|
305
|
+
end
|
306
|
+
second = proc do |_|
|
307
|
+
invoked2 += 1
|
308
|
+
end
|
309
|
+
@bus.subscribe(TestEvent, &second)
|
310
|
+
@bus.publish(event)
|
311
|
+
|
312
|
+
expect(invoked1).to eql 1
|
313
|
+
expect(invoked2).to eql 1
|
314
|
+
|
315
|
+
@bus.unsubscribe(TestEvent, &second)
|
316
|
+
@bus.publish(event)
|
317
|
+
|
318
|
+
expect(invoked1).to eql 2
|
319
|
+
expect(invoked2).to eql 1
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|