u-observers 1.0.0 → 2.0.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 +4 -4
- data/README.md +124 -26
- data/lib/micro/observers.rb +3 -3
- data/lib/micro/observers/event.rb +21 -0
- data/lib/micro/observers/{events.rb → event/names.rb} +1 -1
- data/lib/micro/observers/for/active_model.rb +1 -1
- data/lib/micro/observers/{manager.rb → set.rb} +32 -28
- data/lib/micro/observers/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5098d44090f611866c96aa4005c6475742972a9a986dbbcd5618753932b33817
|
4
|
+
data.tar.gz: 8b18f175cc5ff7fd1aae8e34302be62a1f295a151231c62e253d4b984d0225b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e51002b6af05c7aba17eff30ba962d1d726ce9e169ae19c8a71626c035daf7c1e4e6da727c74f73c06c46ad0f5b1faaad1d1248df018ada97aa123a613e1dedf
|
7
|
+
data.tar.gz: 20972cd57db02b9a25905896af9429a597cfb22e951fe73a3af6346962cd1f3574df30c241b7aa9ef3a99c502097ad0a7d75210a33227a98c6d3435371c8bb03
|
data/README.md
CHANGED
@@ -35,6 +35,9 @@ Because of this issue, I decided to create a gem that encapsulates the pattern w
|
|
35
35
|
- [Compatibility](#compatibility)
|
36
36
|
- [Usage](#usage)
|
37
37
|
- [Passing a context for your observers](#passing-a-context-for-your-observers)
|
38
|
+
- [Passing data when performing observers](#passing-data-when-performing-observers)
|
39
|
+
- [What is a `Micro::Observers::Event`?](#what-is-a-microobserversevent)
|
40
|
+
- [Passing a callable as an observer](#passing-a-callable-as-an-observer)
|
38
41
|
- [Calling the observers](#calling-the-observers)
|
39
42
|
- [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
|
40
43
|
- [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
|
@@ -57,11 +60,12 @@ gem 'u-observers'
|
|
57
60
|
|
58
61
|
| u-observers | branch | ruby | activerecord |
|
59
62
|
| ----------- | ------- | -------- | ------------- |
|
60
|
-
|
|
63
|
+
| 2.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
64
|
+
| 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
61
65
|
|
62
66
|
> **Note**: The ActiveRecord isn't a dependency, but you could add a module to enable some static methods that were designed to be used with its [callbacks](https://guides.rubyonrails.org/active_record_callbacks.html).
|
63
67
|
|
64
|
-
[⬆️ Back to Top](#table-of-contents-)
|
68
|
+
[⬆️ Back to Top](#table-of-contents-)
|
65
69
|
|
66
70
|
## Usage
|
67
71
|
|
@@ -102,8 +106,8 @@ end
|
|
102
106
|
order = Order.new
|
103
107
|
#<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
|
104
108
|
|
105
|
-
order.observers.attach(OrderEvents)
|
106
|
-
# <#Micro::Observers::
|
109
|
+
order.observers.attach(OrderEvents) # attaching multiple observers. e.g. observers.attach(A, B, C)
|
110
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]
|
107
111
|
|
108
112
|
order.canceled?
|
109
113
|
# false
|
@@ -114,6 +118,15 @@ order.cancel!
|
|
114
118
|
|
115
119
|
order.canceled?
|
116
120
|
# true
|
121
|
+
|
122
|
+
order.observers.detach(OrderEvents) # detaching multiple observers. e.g. observers.detach(A, B, C)
|
123
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[]
|
124
|
+
|
125
|
+
order.canceled?
|
126
|
+
# true
|
127
|
+
|
128
|
+
order.observers.subject_changed!
|
129
|
+
order.observers.notify(:canceled) # nothing will happen, because there are no observers attached.
|
117
130
|
```
|
118
131
|
|
119
132
|
**Highlights of the previous example:**
|
@@ -131,12 +144,14 @@ order.observers.notify
|
|
131
144
|
# ArgumentError (no events (expected at least 1))
|
132
145
|
```
|
133
146
|
|
134
|
-
[⬆️ Back to Top](#table-of-contents-)
|
147
|
+
[⬆️ Back to Top](#table-of-contents-)
|
135
148
|
|
136
149
|
### Passing a context for your observers
|
137
150
|
|
138
151
|
To pass a context (any kind of Ruby object) for one or more observers, you will need to use the `context:` keyword as the last argument of the `#attach` method.
|
139
152
|
|
153
|
+
When the observer method receives two arguments, the first one will be the subject, and the second one an instance of `Micro::Observers::Event`.
|
154
|
+
|
140
155
|
```ruby
|
141
156
|
class Order
|
142
157
|
include Micro::Observers
|
@@ -149,19 +164,102 @@ class Order
|
|
149
164
|
end
|
150
165
|
|
151
166
|
module OrderEvents
|
152
|
-
def self.canceled(order,
|
153
|
-
puts "The order #(#{order.
|
167
|
+
def self.canceled(order, event)
|
168
|
+
puts "The order #(#{order.object_id}) has been canceled. (from: #{event.context[:from]})" # event.ctx is an alias for event.context
|
154
169
|
end
|
155
170
|
end
|
156
171
|
|
157
172
|
order = Order.new
|
158
|
-
order.observers.attach(OrderEvents, context: { from: 'example #2' ) # attaching multiple observers. e.g. observers.attach(A, B, context: {hello: :world})
|
173
|
+
order.observers.attach(OrderEvents, context: { from: 'example #2' }) # attaching multiple observers. e.g. observers.attach(A, B, context: {hello: :world})
|
159
174
|
order.cancel!
|
160
175
|
# The message below will be printed by the observer (OrderEvents):
|
161
176
|
# The order #(70196221441820) has been canceled. (from: example #2)
|
162
177
|
```
|
163
178
|
|
164
|
-
[⬆️ Back to Top](#table-of-contents-)
|
179
|
+
[⬆️ Back to Top](#table-of-contents-)
|
180
|
+
|
181
|
+
### Passing data when performing observers
|
182
|
+
|
183
|
+
The [`event context`](#passing-a-context-for-your-observers) is a value that is stored when you attach your observer. But sometimes, will be useful to send some additional data when broadcasting an event to the observers.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class Order
|
187
|
+
include Micro::Observers
|
188
|
+
end
|
189
|
+
|
190
|
+
module OrderHandler
|
191
|
+
def self.changed(order, event)
|
192
|
+
puts "The order #(#{order.object_id}) received the number #{event.data} from #{event.ctx[:from]}."
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
order = Order.new
|
197
|
+
order.observers.attach(OrderHandler, context: { from: 'example #3' })
|
198
|
+
order.observers.subject_changed!
|
199
|
+
order.observers.notify(:changed, data: 1)
|
200
|
+
# The message below will be printed by the observer (OrderHandler):
|
201
|
+
# The order #(70196221441820) received the number 1 from example #3.
|
202
|
+
```
|
203
|
+
|
204
|
+
[⬆️ Back to Top](#table-of-contents-)
|
205
|
+
|
206
|
+
### What is a `Micro::Observers::Event`?
|
207
|
+
|
208
|
+
The `Micro::Observers::Event` is the event payload. Follow below all of its properties:
|
209
|
+
|
210
|
+
- `#name` will be the broadcasted event.
|
211
|
+
- `#subject` will be the observed subject.
|
212
|
+
- `#context` will be [the context data](#passing-a-context-for-your-observers) that was attached to the observer.
|
213
|
+
- `#data` will be [the value that was passed to the observers' notification](#passing-data-when-performing-observers).
|
214
|
+
|
215
|
+
[⬆️ Back to Top](#table-of-contents-)
|
216
|
+
|
217
|
+
### Passing a callable as an observer
|
218
|
+
|
219
|
+
The `observers.on()` method enables you to attach callable as observers. It could receive three options:
|
220
|
+
1. `:event` that will be notified
|
221
|
+
2. `:call` with the callable object.
|
222
|
+
3. `:with` (optional) it can define the value which will be used as the callable object's argument. So, if it receives a `Proc` a `Micro::Observers::Event` instance will be passed to it and the argument will be defined as the `Proc` output. But if this option wasn't be defined, the `Micro::Observers::Event` instance will be its argument.
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
class Person
|
226
|
+
include Micro::Observers
|
227
|
+
|
228
|
+
attr_reader :name
|
229
|
+
|
230
|
+
def initialize(name)
|
231
|
+
@name = name
|
232
|
+
end
|
233
|
+
|
234
|
+
def name=(new_name)
|
235
|
+
observers.subject_changed(new_name != @name)
|
236
|
+
|
237
|
+
return unless observers.subject_changed?
|
238
|
+
|
239
|
+
@name = new_name
|
240
|
+
|
241
|
+
observers.notify(:name_has_been_changed)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
PrintPersonName = -> (data) do
|
246
|
+
puts("Person name: #{data.fetch(:person).name}, number: #{data.fetch(:number)}")
|
247
|
+
end
|
248
|
+
|
249
|
+
person = Person.new('Rodrigo')
|
250
|
+
|
251
|
+
person.observers.on(
|
252
|
+
event: :name_has_been_changed,
|
253
|
+
call: PrintPersonName,
|
254
|
+
with: -> event { {person: event.subject, number: rand} }
|
255
|
+
)
|
256
|
+
|
257
|
+
person.name = 'Serradura'
|
258
|
+
# The message below will be printed by the observer (PrintPersonName):
|
259
|
+
# Person name: Serradura, number: 0.5018509191706862
|
260
|
+
```
|
261
|
+
|
262
|
+
[⬆️ Back to Top](#table-of-contents-)
|
165
263
|
|
166
264
|
### Calling the observers
|
167
265
|
|
@@ -184,13 +282,13 @@ OrderCancellation = -> (order) { puts "The order #(#{order.object_id}) has been
|
|
184
282
|
order = Order.new
|
185
283
|
order.observers.attach(OrderCancellation)
|
186
284
|
order.cancel!
|
187
|
-
# The message below will be printed by the observer (
|
285
|
+
# The message below will be printed by the observer (OrderCancellation):
|
188
286
|
# The order #(70196221441820) has been canceled.
|
189
287
|
```
|
190
288
|
|
191
|
-
|
289
|
+
> **Note**: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.a
|
192
290
|
|
193
|
-
[⬆️ Back to Top](#table-of-contents-)
|
291
|
+
[⬆️ Back to Top](#table-of-contents-)
|
194
292
|
|
195
293
|
### Notifying observers without marking them as changed
|
196
294
|
|
@@ -198,7 +296,7 @@ This feature needs to be used with caution!
|
|
198
296
|
|
199
297
|
If you use the methods `#notify!` or `#call!` you won't need to mark observers with `#subject_changed`.
|
200
298
|
|
201
|
-
[⬆️ Back to Top](#table-of-contents-)
|
299
|
+
[⬆️ Back to Top](#table-of-contents-)
|
202
300
|
|
203
301
|
### ActiveRecord and ActiveModel integrations
|
204
302
|
|
@@ -230,22 +328,22 @@ module TitlePrinter
|
|
230
328
|
end
|
231
329
|
|
232
330
|
module TitlePrinterWithContext
|
233
|
-
def self.after_commit(post,
|
234
|
-
puts "Title: #{post.title} (from: #{context[:from]})"
|
331
|
+
def self.after_commit(post, event)
|
332
|
+
puts "Title: #{post.title} (from: #{event.context[:from]})"
|
235
333
|
end
|
236
334
|
end
|
237
335
|
|
238
336
|
Post.transaction do
|
239
337
|
post = Post.new(title: 'Hello world')
|
240
|
-
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example
|
338
|
+
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #6' })
|
241
339
|
post.save
|
242
340
|
end
|
243
|
-
# The message below will be printed by the
|
341
|
+
# The message below will be printed by the observers (TitlePrinter, TitlePrinterWithContext):
|
244
342
|
# Title: Hello world
|
245
|
-
# Title: Hello world (from: example
|
343
|
+
# Title: Hello world (from: example #6)
|
246
344
|
```
|
247
345
|
|
248
|
-
[⬆️ Back to Top](#table-of-contents-)
|
346
|
+
[⬆️ Back to Top](#table-of-contents-)
|
249
347
|
|
250
348
|
#### notify_observers()
|
251
349
|
|
@@ -265,24 +363,24 @@ module TitlePrinter
|
|
265
363
|
end
|
266
364
|
|
267
365
|
module TitlePrinterWithContext
|
268
|
-
def self.transaction_completed(post,
|
269
|
-
puts("Title: #{post.title} (from: #{
|
366
|
+
def self.transaction_completed(post, event)
|
367
|
+
puts("Title: #{post.title} (from: #{event.ctx[:from]})")
|
270
368
|
end
|
271
369
|
end
|
272
370
|
|
273
371
|
Post.transaction do
|
274
372
|
post = Post.new(title: 'Olá mundo')
|
275
|
-
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example
|
373
|
+
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #7' })
|
276
374
|
post.save
|
277
375
|
end
|
278
|
-
# The message below will be printed by the
|
376
|
+
# The message below will be printed by the observers (TitlePrinter, TitlePrinterWithContext):
|
279
377
|
# Title: Olá mundo
|
280
|
-
# Title: Olá mundo (from: example 5)
|
378
|
+
# Title: Olá mundo (from: example #5)
|
281
379
|
```
|
282
380
|
|
283
|
-
|
381
|
+
> **Note**: You can use `include ::Micro::Observers::For::ActiveModel` if your class only makes use of the `ActiveModel` and all the previous examples will work.
|
284
382
|
|
285
|
-
[⬆️ Back to Top](#table-of-contents-)
|
383
|
+
[⬆️ Back to Top](#table-of-contents-)
|
286
384
|
|
287
385
|
## Development
|
288
386
|
|
data/lib/micro/observers.rb
CHANGED
@@ -3,11 +3,11 @@ require 'micro/observers/version'
|
|
3
3
|
module Micro
|
4
4
|
module Observers
|
5
5
|
require 'micro/observers/utils'
|
6
|
-
require 'micro/observers/
|
7
|
-
require 'micro/observers/
|
6
|
+
require 'micro/observers/event'
|
7
|
+
require 'micro/observers/set'
|
8
8
|
|
9
9
|
def observers
|
10
|
-
@__observers ||= Observers::
|
10
|
+
@__observers ||= Observers::Set.for(self)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro
|
4
|
+
module Observers
|
5
|
+
|
6
|
+
class Event
|
7
|
+
require 'micro/observers/event/names'
|
8
|
+
|
9
|
+
attr_reader :name, :subject, :context, :data
|
10
|
+
|
11
|
+
def initialize(name, subject, context, data)
|
12
|
+
@name, @subject = name, subject
|
13
|
+
@context, @data = context, data
|
14
|
+
end
|
15
|
+
|
16
|
+
alias ctx context
|
17
|
+
alias subj subject
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Micro
|
4
4
|
module Observers
|
5
5
|
|
6
|
-
class
|
6
|
+
class Set
|
7
7
|
MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
|
8
8
|
|
9
9
|
MapSubscribers = -> (value) do
|
@@ -46,11 +46,7 @@ module Micro
|
|
46
46
|
INVALID_BOOLEAN_MSG = 'expected a boolean (true, false)'.freeze
|
47
47
|
|
48
48
|
def subject_changed(state)
|
49
|
-
if state == true || state == false
|
50
|
-
@subject_changed = state
|
51
|
-
|
52
|
-
return self
|
53
|
-
end
|
49
|
+
return @subject_changed = state if state == true || state == false
|
54
50
|
|
55
51
|
raise ArgumentError, INVALID_BOOLEAN_MSG
|
56
52
|
end
|
@@ -91,28 +87,28 @@ module Micro
|
|
91
87
|
self
|
92
88
|
end
|
93
89
|
|
94
|
-
def notify(*events)
|
95
|
-
broadcast_if_subject_changed(
|
90
|
+
def notify(*events, data: nil)
|
91
|
+
broadcast_if_subject_changed(Event::Names.fetch(events), data)
|
96
92
|
|
97
93
|
self
|
98
94
|
end
|
99
95
|
|
100
|
-
def notify!(*events)
|
101
|
-
broadcast(
|
96
|
+
def notify!(*events, data: nil)
|
97
|
+
broadcast(Event::Names.fetch(events), data)
|
102
98
|
|
103
99
|
self
|
104
100
|
end
|
105
101
|
|
106
102
|
CALL_EVENT = [:call].freeze
|
107
103
|
|
108
|
-
def call(*events)
|
109
|
-
broadcast_if_subject_changed(
|
104
|
+
def call(*events, data: nil)
|
105
|
+
broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
|
110
106
|
|
111
107
|
self
|
112
108
|
end
|
113
109
|
|
114
|
-
def call!(*events)
|
115
|
-
broadcast(
|
110
|
+
def call!(*events, data: nil)
|
111
|
+
broadcast(Event::Names[events, default: CALL_EVENT], data)
|
116
112
|
|
117
113
|
self
|
118
114
|
end
|
@@ -125,43 +121,51 @@ module Micro
|
|
125
121
|
|
126
122
|
private
|
127
123
|
|
128
|
-
def broadcast_if_subject_changed(events)
|
124
|
+
def broadcast_if_subject_changed(events, data = nil)
|
129
125
|
return unless subject_changed?
|
130
126
|
|
131
|
-
broadcast(events)
|
127
|
+
broadcast(events, data)
|
132
128
|
|
133
129
|
subject_changed(false)
|
134
130
|
end
|
135
131
|
|
136
|
-
def broadcast(
|
132
|
+
def broadcast(event_names, data)
|
137
133
|
return if @subscribers.empty?
|
138
134
|
|
139
|
-
|
135
|
+
event_names.each do |event_name|
|
140
136
|
@subscribers.each do |strategy, observer, context|
|
141
137
|
case strategy
|
142
|
-
when :observer then notify_observer(observer,
|
143
|
-
when :callable then notify_callable(observer,
|
138
|
+
when :observer then notify_observer(observer, event_name, context, data)
|
139
|
+
when :callable then notify_callable(observer, event_name, context, data)
|
144
140
|
end
|
145
141
|
end
|
146
142
|
end
|
147
143
|
end
|
148
144
|
|
149
|
-
def notify_observer(observer,
|
150
|
-
return unless observer.respond_to?(
|
145
|
+
def notify_observer(observer, event_name, context, data)
|
146
|
+
return unless observer.respond_to?(event_name)
|
151
147
|
|
152
|
-
handler = observer.is_a?(Proc) ? observer : observer.method(
|
148
|
+
handler = observer.is_a?(Proc) ? observer : observer.method(event_name)
|
153
149
|
|
154
|
-
|
150
|
+
return handler.call(@subject) if handler.arity == 1
|
151
|
+
|
152
|
+
handler.call(@subject, Event.new(event_name, @subject, context, data))
|
155
153
|
end
|
156
154
|
|
157
|
-
def notify_callable(
|
158
|
-
return if
|
155
|
+
def notify_callable(expected_event_name, event_name, context, data)
|
156
|
+
return if expected_event_name != event_name
|
159
157
|
|
160
158
|
callable, with = context[0], context[1]
|
159
|
+
callable_arg =
|
160
|
+
if with && !with.is_a?(Proc)
|
161
|
+
with
|
162
|
+
else
|
163
|
+
event = Event.new(event_name, @subject, nil, data)
|
161
164
|
|
162
|
-
|
165
|
+
with.is_a?(Proc) ? with.call(event) : event
|
166
|
+
end
|
163
167
|
|
164
|
-
callable.call(
|
168
|
+
callable.call(callable_arg)
|
165
169
|
end
|
166
170
|
|
167
171
|
private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-observers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -57,10 +57,11 @@ files:
|
|
57
57
|
- bin/console
|
58
58
|
- bin/setup
|
59
59
|
- lib/micro/observers.rb
|
60
|
-
- lib/micro/observers/
|
60
|
+
- lib/micro/observers/event.rb
|
61
|
+
- lib/micro/observers/event/names.rb
|
61
62
|
- lib/micro/observers/for/active_model.rb
|
62
63
|
- lib/micro/observers/for/active_record.rb
|
63
|
-
- lib/micro/observers/
|
64
|
+
- lib/micro/observers/set.rb
|
64
65
|
- lib/micro/observers/utils.rb
|
65
66
|
- lib/micro/observers/version.rb
|
66
67
|
- lib/u-observers.rb
|