aether_observatory 0.0.1pre4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 61d7e1ed3ada76ff7ac750bc1a617c90a3280f53468ca76e33e77be8bbc14a4b
4
+ data.tar.gz: c218497df0007226254a4bdea92856247d60ece8ef6738e541b08b499ffbd6cf
5
+ SHA512:
6
+ metadata.gz: 7a6067081b5a3c1a98fb968d228e003a352132ee909dc6488a45d978626a065202ca72547ee203eae5ca57106a9bc46b55afde8498aac2ac8a44b38fa599858c
7
+ data.tar.gz: bcf7b0ef815d37ea110cb0996e8be8afe5612235b8857df27412b06dbde2470d1f9fc9c1925dbd881de3dfdc4f862166ea724925d03a08ef0a989a3ff8e878a4
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ require:
2
+ - rubocop-powerhome
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.0
6
+
7
+ Metrics/MethodLength:
8
+ Exclude:
9
+ - spec/**/*_spec.rb
10
+
11
+ Style/FrozenStringLiteralComment:
12
+ Exclude:
13
+ - 'gemfiles/*'
14
+
15
+ Bundler/OrderedGems:
16
+ Exclude:
17
+ - 'gemfiles/*'
data/Appraisals ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise "rails-6-0" do
4
+ gem "rails", "~> 6.0.6"
5
+ end
6
+
7
+ appraise "rails-6-1" do
8
+ gem "rails", "~> 6.1.7"
9
+ end
10
+
11
+ appraise "rails-7-0" do
12
+ gem "rails", "~> 7.0.8"
13
+ end
14
+
15
+ appraise "rails-7-1" do
16
+ gem "rails", "~> 7.1.3"
17
+ end
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rubocop-powerhome", path: "../rubocop-powerhome"
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env rake
2
+
3
+ # frozen_string_literal: true
4
+
5
+ begin
6
+ require "bundler/setup"
7
+ rescue LoadError
8
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
9
+ end
10
+ Bundler::GemHelper.install_tasks
11
+
12
+ require "rspec/core/rake_task"
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ require "rubocop/rake_task"
16
+ RuboCop::RakeTask.new(:rubocop)
17
+
18
+ require "yard"
19
+ YARD::Rake::YardocTask.new do |t|
20
+ t.files = ["lib/**/*.rb"]
21
+ t.options = [
22
+ "--no-private",
23
+ ]
24
+ end
25
+
26
+ task default: %i[rubocop spec]
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/aether_observatory/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "aether_observatory"
7
+ spec.version = AetherObservatory::VERSION
8
+ spec.authors = ["Terry Finn", "Justin Stanczak"]
9
+ spec.email = ["terry.finn@powerhrg.com", "justin.stanczak@powerhrg.com"]
10
+
11
+ spec.summary = "Aether Observatory"
12
+ spec.description = "Aether Observatory provides an event broadcast system."
13
+ spec.homepage = "https://github.com/powerhome/power-tools"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0"
16
+
17
+ spec.metadata["rubygems_mfa_required"] = "true"
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/packages/aether_observatory/docs/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
27
+ end
28
+ end
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "activemodel", ">= 6.0.6.1"
32
+ spec.add_dependency "activesupport", ">= 6.0.6.1"
33
+ spec.add_development_dependency "appraisal", "~> 2.5.0"
34
+
35
+ spec.add_development_dependency "bundler", "~> 2.1"
36
+ spec.add_development_dependency "license_finder", "~> 7.0"
37
+ spec.add_development_dependency "pry", ">= 0.14"
38
+ spec.add_development_dependency "pry-byebug", "3.10.1"
39
+ spec.add_development_dependency "rake", "~> 13.0"
40
+ spec.add_development_dependency "rspec", "~> 3.0"
41
+ spec.add_development_dependency "simplecov", "0.15.1"
42
+ spec.add_development_dependency "yard", "0.9.21"
43
+ spec.metadata["rubygems_mfa_required"] = "true"
44
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ - - :inherit_from
3
+ - https://raw.githubusercontent.com/powerhome/oss-guide/master/license_rules.yml
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.0.1] - 2024-12-06
2
+
3
+ - Extracts AetherObservatory from Talkbox engine.
data/docs/README.md ADDED
@@ -0,0 +1,368 @@
1
+ # AetherObservatory Guide
2
+
3
+ In this guide we are going to walk through example code to illustrate the
4
+ usage of the `AetherObservatory::`. When finished you will have a class to
5
+ create events and a class that subscribes to those events.
6
+
7
+ #### Table of Contents
8
+ - [Creating Events](#creating-events)
9
+ - [Creating an Observer and Subscribing to Events](#creating-an-observer-and-subscribing-to-events)
10
+ - [Sending an Event to your Observer](#sending-an-event-to-your-observer)
11
+ - [Stopping Observers](#stopping-observers)
12
+ - [Using Dynamic Event Names](#using-dynamic-event-names)
13
+ - [Multiple Event Topics](#multiple-event-topics)
14
+
15
+ ## Creating Events
16
+
17
+ To begin create an `ApplicationEvent` class that extends the
18
+ `AetherObservatory::EventBase` class. Next configure a prefix for event
19
+ names using `event_prefix`. This is optional, but encouraged to help prevent
20
+ naming collisions with other domains. Every domain event we define as a
21
+ sub-class to the `ApplicationEvent` will inherit this prefix.
22
+
23
+ ```ruby
24
+ module AetherObservatory
25
+ module Examples
26
+ class ApplicationEvent < AetherObservatory::EventBase
27
+ event_prefix 'talkbox'
28
+ end
29
+ end
30
+ end
31
+ ```
32
+
33
+ Next we create an event class called `ExampleEvent` that extends our
34
+ `ApplicationEvent`. In this class we define the topic we would like our
35
+ event sent to using the `event_name` method. Lastly we will define our
36
+ data using the `attribute` method.
37
+
38
+ ```ruby
39
+ module AetherObservatory
40
+ module Examples
41
+ class ExampleEvent < AetherObservatory::Examples::ApplicationEvent
42
+ event_name 'example1'
43
+
44
+ attribute :message
45
+ attribute :timestamp, default: -> { Time.current }
46
+ end
47
+ end
48
+ end
49
+ ```
50
+
51
+ Now we have a class to create new events. Each time you create a new event,
52
+ it will be sent to each topic you added via the `event_name` method.
53
+
54
+ ```ruby
55
+ AetherObservatory::Examples::ExampleEvent.create(message: 'hello world')
56
+ ```
57
+
58
+ Running the command above will display a log message like you see below.
59
+
60
+ ```irb
61
+ irb(main):018:0> AetherObservatory::Examples::ExampleEvent.create(message: 'hello world')
62
+ [AetherObservatory::Examples::ExampleEvent] Create event for topic: [talkbox.example1]
63
+ => nil
64
+ irb(main):019:0>
65
+ ```
66
+
67
+ Now that we have an `ExampleEvent` class to create events we need to create
68
+ an observer to listen for those events.
69
+
70
+ <div align="right">
71
+ <a href="#aetherobservatory-guide">Top</a>
72
+ </div>
73
+
74
+ ## Creating an Observer and Subscribing to Events
75
+
76
+ Our new event class `ExampleEvent` creates a new event on the
77
+ `talkbox.example1` topic so this is the topic we need to create a observer for.
78
+
79
+ We start by creating another class called `ExampleObserver` that extends
80
+ the `AetherObservatory::ObserverBase` class. Next we use the `subscribe_to`
81
+ method to register this observer to the topic `talkbox.example1`. We also
82
+ need to define a `process` method that will be called each time your observer
83
+ receives an event. In this `process` method you have access to `event_payload`
84
+ and `event_name` objects for your own logic.
85
+
86
+ ```ruby
87
+ module AetherObservatory
88
+ module Examples
89
+ class ExampleObserver < AetherObservatory::ObserverBase
90
+ subscribe_to 'talkbox.example1'
91
+
92
+ def process
93
+ puts <<-EVENT
94
+ ************************************
95
+ Event processed:
96
+ Name: #{event_name.inspect}
97
+ Message: #{event_payload.message}
98
+ Timestamp: #{event_payload.timestamp}
99
+ Event Payload: #{event_payload.inspect}
100
+ ************************************
101
+ EVENT
102
+ end
103
+ end
104
+ end
105
+ end
106
+ ```
107
+ Now that we have a new observer named `ExampleObserver`, we will need to
108
+ start our observer before it will process any events. Observers default
109
+ to `stopped`, so we need to call `start` on each observer before they will
110
+ recieve events. Inside an initilizer is the recommended location to start
111
+ your observers.
112
+
113
+ ```ruby
114
+ AetherObservatory::Examples::ExampleObserver.start
115
+ ```
116
+
117
+ <div align="right">
118
+ <a href="#aetherobservatory-guide">Top</a>
119
+ </div>
120
+
121
+ ## Sending an Event to your Observer
122
+
123
+ Now that you have all your classes created you can send events to your
124
+ observer via the `create` method.
125
+
126
+ ```ruby
127
+ AetherObservatory::Examples::ExampleEvent.create(message: 'hello world')
128
+ ```
129
+
130
+ Calling create on your `ExampleEvent` class will trigger the `process`
131
+ method in the `ExampleObserver` class. You should see the following logged
132
+ output.
133
+
134
+ ```irb
135
+ irb(main):040:0> AetherObservatory::Examples::ExampleEvent.create(message: 'hello world')
136
+ ************************************
137
+ Event processed:
138
+ Name: "talkbox.example1"
139
+ Message: hello world
140
+ Timestamp: 2024-05-23 15:17:16 UTC
141
+ Event Payload: #<AetherObservatory::Examples::ExampleEvent:0x0000aaaadc2b2118 @attributes=#<ActiveModel::AttributeSet:0x0000aaaadc2b1f38 @attributes={"message"=>#<ActiveModel::Attribute::FromUser:0x0000aaaadc2b1fb0 @name="message", @value_before_type_cast="hello world", @type=#<ActiveModel::Type::Value:0x0000aaaadc101d28 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=#<ActiveModel::Attribute::WithCastValue:0x0000aaaadc2b2dc0 @name="message", @value_before_type_cast=nil, @type=#<ActiveModel::Type::Value:0x0000aaaadc101d28 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil>, @value="hello world">, "timestamp"=>#<ActiveModel::Attribute::UserProvidedDefault:0x0000aaaadc2b1f60 @user_provided_value=#<Proc:0x0000aaaadc0f3b38 (irb):15 (lambda)>, @name="timestamp", @value_before_type_cast=#<Proc:0x0000aaaadc0f3b38 (irb):15 (lambda)>, @type=#<ActiveModel::Type::Value:0x0000aaaadc0f3ac0 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil, @memoized_value_before_type_cast=Thu, 23 May 2024 15:17:16.082153128 UTC +00:00, @value=Thu, 23 May 2024 15:17:16.082153128 UTC +00:00>}>>
142
+ ************************************
143
+ [AetherObservatory::Examples::ExampleEvent] Create event for topic: [talkbox.example1]
144
+ => nil
145
+ ```
146
+
147
+ <div align="right">
148
+ <a href="#aetherobservatory-guide">Top</a>
149
+ </div>
150
+
151
+ ## Stopping Observers
152
+
153
+ To stop your observer from processing events you can call the `stop` method
154
+ on your observer class. This stops only that observer class from processing
155
+ events.
156
+
157
+ ```ruby
158
+ AetherObservatory::Examples::ExampleObserver.stop
159
+ ```
160
+
161
+ <div align="right">
162
+ <a href="#aetherobservatory-guide">Top</a>
163
+ </div>
164
+
165
+ ## Using Dynamic Event Names
166
+
167
+ Create a new class called `RandomEvent` that extends `ApplicationEvent`.
168
+ Then pass a block to the `event_name` method. This allows you to dynamiclly
169
+ select your topic at the time of event creation.
170
+
171
+ <sup>*Note: [ApplicationEvent](#creating-events) class was created at the
172
+ beginning of this guide.*</sup>
173
+
174
+ ```ruby
175
+ module AetherObservatory
176
+ module Examples
177
+ class RandomEvent < AetherObservatory::Examples::ApplicationEvent
178
+ event_name { select_a_topic_at_random }
179
+
180
+ attribute :message
181
+
182
+ private
183
+
184
+ def select_a_topic_at_random
185
+ %w(test support customer).sample
186
+ end
187
+ end
188
+ end
189
+ end
190
+ ```
191
+
192
+ You can now create a few events with your new class using the `create`
193
+ method of that class.
194
+
195
+ ```ruby
196
+ AetherObservatory::Examples::RandomEvent.create(message: 'hello world')
197
+ ```
198
+
199
+ As you can see from the following output a random event name is selected
200
+ each time you call `create`.
201
+
202
+ ```irb
203
+ irb(main):078:0> AetherObservatory::Examples::RandomEvent.create(message: 'hello world')
204
+ [AetherObservatory::Examples::RandomEvent] Create event for topic: [talkbox.support]
205
+ => nil
206
+ irb(main):079:0> AetherObservatory::Examples::RandomEvent.create(message: 'hello world')
207
+ [AetherObservatory::Examples::RandomEvent] Create event for topic: [talkbox.test]
208
+ => nil
209
+ irb(main):080:0> AetherObservatory::Examples::RandomEvent.create(message: 'hello world')
210
+ [AetherObservatory::Examples::RandomEvent] Create event for topic: [talkbox.support]
211
+ => nil
212
+ irb(main):081:0> AetherObservatory::Examples::RandomEvent.create(message: 'hello world')
213
+ [AetherObservatory::Examples::RandomEvent] Create event for topic: [talkbox.customer]
214
+ => nil
215
+ ```
216
+
217
+ <div align="right">
218
+ <a href="#aetherobservatory-guide">Top</a>
219
+ </div>
220
+
221
+ ## Multiple Event Topics
222
+
223
+ In this example we are going to create an event class that sends events to
224
+ two different topics based on the `level` attribute from the event class.
225
+ We are also going to make two observer classes that subscribe to different
226
+ events based on their role in the system.
227
+
228
+ <sup>*Note: [ApplicationEvent](#creating-events) class was created at the
229
+ beginning of this guide.*</sup>
230
+
231
+ We first create the `TalkboxCallQueueEvent` class. This class will send each
232
+ event to the `talkbox.call_queues.events.all` topic and to the `level` scoped
233
+ topic.
234
+
235
+ ```ruby
236
+ module AetherObservatory
237
+ module Examples
238
+ class TalkboxCallQueueEvent < AetherObservatory::Examples::ApplicationEvent
239
+ event_name 'call_queues.events.all'
240
+ event_name { "call_queues.events.#{level}" }
241
+
242
+ attribute :level, default: 'info'
243
+ end
244
+ end
245
+ end
246
+ ```
247
+
248
+ The new `TalkboxCallQueueEvent` class will send all events to the `all`
249
+ topic. However the events will also be sent to their specific event `level`
250
+ scoped topic. This allows us to have one observer logging call history and
251
+ a second observer that handles events with the scoped `level` or error for
252
+ topic `talkbox.call_queues.events.error`.
253
+
254
+ Next we need to create a new class called `TalkboxCallHistoryObserver`. This
255
+ observer will subscribe to the `talkbox.call_queues.events.all` topic. This
256
+ classes function is to record all call queue events.
257
+
258
+ ```ruby
259
+ module AetherObservatory
260
+ module Examples
261
+ class TalkboxCallHistoryObserver < AetherObservatory::ObserverBase
262
+ subscribe_to 'talkbox.call_queues.events.all'
263
+
264
+ delegate :level, to: :event_payload
265
+
266
+ def process
267
+ puts <<-EVENT
268
+ ************************************
269
+ Event processed:
270
+ Name: #{event_name.inspect}
271
+ Level: #{event_payload.level}
272
+ Event Payload: #{event_payload.inspect}
273
+ ************************************
274
+ EVENT
275
+ end
276
+ end
277
+ end
278
+ end
279
+ ```
280
+
281
+ Next we need a class called `TalkboxCallErrorObserver`. This class only
282
+ subscribes to the `talkbox.call_queues.events.error` topic. It only cares
283
+ about `error` level events and nothing else.
284
+
285
+ ```ruby
286
+ module AetherObservatory
287
+ module Examples
288
+ class TalkboxCallErrorObserver < AetherObservatory::ObserverBase
289
+ subscribe_to 'talkbox.call_queues.events.error'
290
+
291
+ def process
292
+ puts <<-EVENT
293
+ ************************************
294
+ Error Event processed:
295
+ Name: #{event_name.inspect}
296
+ Level: #{event_payload.level}
297
+ Event Payload: #{event_payload.inspect}
298
+ ************************************
299
+ EVENT
300
+ end
301
+ end
302
+ end
303
+ end
304
+ ```
305
+
306
+ We need to be sure to start our new observers before they will recieve
307
+ any events.
308
+
309
+ ```ruby
310
+ AetherObservatory::Examples::TalkboxCallHistoryObserver.start
311
+ AetherObservatory::Examples::TalkboxCallErrorObserver.start
312
+ ```
313
+
314
+ Finally we are ready to create a new event and see what happens. First we
315
+ create an event with a default level.
316
+
317
+ ```ruby
318
+ AetherObservatory::Examples::TalkboxCallQueueEvent.create
319
+ ```
320
+
321
+ Running the create with no parameters will have a default level of `info`.
322
+ You will see the following output.
323
+
324
+ ```irb
325
+ irb(main):058:0> AetherObservatory::Examples::TalkboxCallQueueEvent.create
326
+ ************************************
327
+ Event processed:
328
+ Name: "talkbox.call_queues.events.all"
329
+ Level: info
330
+ Event Payload: #<AetherObservatory::Examples::TalkboxCallQueueEvent:0x0000aaab112f75d0 @attributes=#<ActiveModel::AttributeSet:0x0000aaab112f5e88 @attributes={"level"=>#<ActiveModel::Attribute::UserProvidedDefault:0x0000aaab112f73a0 @user_provided_value="info", @name="level", @value_before_type_cast="info", @type=#<ActiveModel::Type::Value:0x0000aaab13a76e08 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil, @value="info">}>>
331
+ ************************************
332
+ [AetherObservatory::Examples::TalkboxCallQueueEvent] Create event for topic: [talkbox.call_queues.events.all]
333
+ [AetherObservatory::Examples::TalkboxCallQueueEvent] Create event for topic: [talkbox.call_queues.events.info]
334
+ => nil
335
+ ```
336
+
337
+ Next we will try creating a new event but this time we set the `level`
338
+ to `error`.
339
+
340
+ ```ruby
341
+ AetherObservatory::Examples::TalkboxCallQueueEvent.create(level: 'error')
342
+ ```
343
+
344
+ As you can see from the output, setting the `level` to `error` will send
345
+ an event to both classes.
346
+
347
+ ```irb
348
+ irb(main):059:0> AetherObservatory::Examples::TalkboxCallQueueEvent.create(level: 'error')
349
+ ************************************
350
+ Event processed:
351
+ Name: "talkbox.call_queues.events.all"
352
+ Level: error
353
+ Event Payload: #<AetherObservatory::Examples::TalkboxCallQueueEvent:0x0000aaab135cff30 @attributes=#<ActiveModel::AttributeSet:0x0000aaab135cfe18 @attributes={"level"=>#<ActiveModel::Attribute::FromUser:0x0000aaab135cfe68 @name="level", @value_before_type_cast="error", @type=#<ActiveModel::Type::Value:0x0000aaab13a76e08 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=#<ActiveModel::Attribute::UserProvidedDefault:0x0000aaab135e0bc8 @user_provided_value="info", @name="level", @value_before_type_cast="info", @type=#<ActiveModel::Type::Value:0x0000aaab13a76e08 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil>, @value="error">}>>
354
+ ************************************
355
+ [AetherObservatory::Examples::TalkboxCallQueueEvent] Create event for topic: [talkbox.call_queues.events.all]
356
+ ************************************
357
+ Error Event processed:
358
+ Name: "talkbox.call_queues.events.error"
359
+ Level: error
360
+ Event Payload: #<AetherObservatory::Examples::TalkboxCallQueueEvent:0x0000aaab135cef90 @attributes=#<ActiveModel::AttributeSet:0x0000aaab135ceea0 @attributes={"level"=>#<ActiveModel::Attribute::FromUser:0x0000aaab135cef40 @name="level", @value_before_type_cast="error", @type=#<ActiveModel::Type::Value:0x0000aaab13a76e08 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=#<ActiveModel::Attribute::UserProvidedDefault:0x0000aaab135e0bc8 @user_provided_value="info", @name="level", @value_before_type_cast="info", @type=#<ActiveModel::Type::Value:0x0000aaab13a76e08 @precision=nil, @scale=nil, @limit=nil>, @original_attribute=nil>, @value="error">}>>
361
+ ************************************
362
+ [AetherObservatory::Examples::TalkboxCallQueueEvent] Create event for topic: [talkbox.call_queues.events.error]
363
+ => nil
364
+ ```
365
+
366
+ <div align="right">
367
+ <a href="#aetherobservatory-guide">Top</a>
368
+ </div>
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop-powerhome", path: "../../rubocop-powerhome"
6
+ gem "rails", "~> 6.0.6"
7
+
8
+ gemspec path: "../"