u-observers 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|