octiron 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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