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.
@@ -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
@@ -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