aether_observatory 0.0.1pre4

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 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: "../"