u-observers 2.0.0 → 2.1.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 +54 -31
- data/README.pt-BR.md +426 -0
- data/lib/micro/observers/event/names.rb +4 -2
- data/lib/micro/observers/for/active_model.rb +1 -1
- data/lib/micro/observers/set.rb +14 -12
- data/lib/micro/observers/utils.rb +4 -5
- data/lib/micro/observers/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52f8cf4c9f76af4b9438412dc60fa7b4156fa84afec92f89a12bb40b90d1ed32
|
4
|
+
data.tar.gz: 1c1ad202a47f467e468186b58671bed796656180660302f057005a7387bdcc7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2f779070c6e11ef08c71b85f9d90342907cef3e910e7f6a7b61bbebeeec283baf56d197df1edf3bb4bb7437424e43d74208d97fed1e745b76367b2c14fb2206
|
7
|
+
data.tar.gz: 7995b96fe8868e126fd053e05e831b92adc421325b133324222591c74a288ba4de9677dc59b2b5f57a8d8a437c04a342f04a9548ed460546e9f56b571e4ac3c5
|
data/README.md
CHANGED
@@ -26,18 +26,20 @@
|
|
26
26
|
|
27
27
|
This gem implements the observer pattern [[1]](https://en.wikipedia.org/wiki/Observer_pattern)[[2]](https://refactoring.guru/design-patterns/observer) (also known as publish/subscribe). It provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.
|
28
28
|
|
29
|
-
Ruby's standard library [has an abstraction](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html) that enables you to use this pattern. But its design can conflict with other mainstream libraries, like the [`ActiveModel`/`ActiveRecord`](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed), which also has the [`changed`](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html#method-i-changed) method. In this case, the behavior of the Stdlib will be
|
29
|
+
Ruby's standard library [has an abstraction](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html) that enables you to use this pattern. But its design can conflict with other mainstream libraries, like the [`ActiveModel`/`ActiveRecord`](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed), which also has the [`changed`](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html#method-i-changed) method. In this case, the behavior of the Stdlib will be compromised.
|
30
30
|
|
31
|
-
Because of this issue, I decided to create a gem that encapsulates the pattern without changing the object's implementation so much. The `Micro::Observers` includes just one instance method in the target class (its instance will be the observed subject).
|
31
|
+
Because of this issue, I decided to create a gem that encapsulates the pattern without changing the object's implementation so much. The `Micro::Observers` includes just one instance method in the target class (its instance will be the observed subject/object).
|
32
|
+
|
33
|
+
> **Note:** Você entende português? 🇧🇷 🇵🇹 Verifique o [README traduzido em pt-BR](https://github.com/serradura/u-observers/blob/main/README.pt-BR.md).
|
32
34
|
|
33
35
|
# Table of contents <!-- omit in toc -->
|
34
36
|
- [Installation](#installation)
|
35
37
|
- [Compatibility](#compatibility)
|
36
38
|
- [Usage](#usage)
|
37
|
-
- [
|
38
|
-
- [
|
39
|
+
- [Sharing a context with your observers](#sharing-a-context-with-your-observers)
|
40
|
+
- [Sharing data when notifying the observers](#sharing-data-when-notifying-the-observers)
|
39
41
|
- [What is a `Micro::Observers::Event`?](#what-is-a-microobserversevent)
|
40
|
-
- [
|
42
|
+
- [Using a callable as an observer](#using-a-callable-as-an-observer)
|
41
43
|
- [Calling the observers](#calling-the-observers)
|
42
44
|
- [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
|
43
45
|
- [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
|
@@ -60,7 +62,8 @@ gem 'u-observers'
|
|
60
62
|
|
61
63
|
| u-observers | branch | ruby | activerecord |
|
62
64
|
| ----------- | ------- | -------- | ------------- |
|
63
|
-
|
|
65
|
+
| unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
66
|
+
| 2.1.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
64
67
|
| 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
65
68
|
|
66
69
|
> **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).
|
@@ -107,7 +110,7 @@ order = Order.new
|
|
107
110
|
#<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
|
108
111
|
|
109
112
|
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]
|
113
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]>
|
111
114
|
|
112
115
|
order.canceled?
|
113
116
|
# false
|
@@ -120,7 +123,7 @@ order.canceled?
|
|
120
123
|
# true
|
121
124
|
|
122
125
|
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=[]
|
126
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[]>
|
124
127
|
|
125
128
|
order.canceled?
|
126
129
|
# true
|
@@ -131,7 +134,7 @@ order.observers.notify(:canceled) # nothing will happen, because there are no ob
|
|
131
134
|
|
132
135
|
**Highlights of the previous example:**
|
133
136
|
|
134
|
-
To avoid an undesired behavior,
|
137
|
+
To avoid an undesired behavior, you need to mark the subject as changed before notifying your observers about some event.
|
135
138
|
|
136
139
|
You can do this when using the `#subject_changed!` method. It will automatically mark the subject as changed.
|
137
140
|
|
@@ -146,11 +149,11 @@ order.observers.notify
|
|
146
149
|
|
147
150
|
[⬆️ Back to Top](#table-of-contents-)
|
148
151
|
|
149
|
-
###
|
152
|
+
### Sharing a context with your observers
|
150
153
|
|
151
|
-
To
|
154
|
+
To share a context value (any kind of Ruby object) with one or more observers, you will need to use the `:context` keyword as the last argument of the `#attach` method. This feature gives you a unique opportunity to share a value in the attaching moment.
|
152
155
|
|
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
|
156
|
+
When the observer method receives two arguments, the first one will be the subject, and the second one an instance of `Micro::Observers::Event` that will have the given context value.
|
154
157
|
|
155
158
|
```ruby
|
156
159
|
class Order
|
@@ -178,9 +181,9 @@ order.cancel!
|
|
178
181
|
|
179
182
|
[⬆️ Back to Top](#table-of-contents-)
|
180
183
|
|
181
|
-
###
|
184
|
+
### Sharing data when notifying the observers
|
182
185
|
|
183
|
-
|
186
|
+
As previously mentioned, the [`event context`](#sharing-a-context-with-your-observers) is a value that is stored when you attach your observer. But sometimes, it will be useful to send some additional data when broadcasting an event to the observers. The `event data` gives you this unique opportunity to share some value at the the notification moment.
|
184
187
|
|
185
188
|
```ruby
|
186
189
|
class Order
|
@@ -209,17 +212,24 @@ The `Micro::Observers::Event` is the event payload. Follow below all of its prop
|
|
209
212
|
|
210
213
|
- `#name` will be the broadcasted event.
|
211
214
|
- `#subject` will be the observed subject.
|
212
|
-
- `#context` will be [the context data](#
|
213
|
-
- `#data` will be [the value that was
|
215
|
+
- `#context` will be [the context data](#sharing-a-context-with-your-observers) that was defined in the moment that you attach the observer.
|
216
|
+
- `#data` will be [the value that was shared in the observers' notification](#sharing-data-when-notifying-the-observers).
|
217
|
+
- `#ctx` is an alias for the `#context` method.
|
218
|
+
- `#subj` is an alias for the `#subject` method.
|
214
219
|
|
215
220
|
[⬆️ Back to Top](#table-of-contents-)
|
216
221
|
|
217
|
-
###
|
222
|
+
### Using a callable as an observer
|
223
|
+
|
224
|
+
The `observers.on()` method enables you to attach a callable as an observer.
|
225
|
+
|
226
|
+
Usually, a callable has a well-defined responsibility (do only one thing), because of this, it tends to be more [SRP (Single-responsibility principle)](https://en.wikipedia.org/wiki/Single-responsibility_principle). friendly than a conventional observer (that could have N methods to respond to different kinds of notification).
|
218
227
|
|
219
|
-
|
220
|
-
1. `:event`
|
221
|
-
2. `:call`
|
222
|
-
3. `:with` (optional) it can define the value which will be used as the callable object's argument. So, if it
|
228
|
+
This method receives the below options:
|
229
|
+
1. `:event` the expected event name.
|
230
|
+
2. `:call` the callable object itself.
|
231
|
+
3. `:with` (optional) it can define the value which will be used as the callable object's argument. So, if it is a `Proc`, a `Micro::Observers::Event` instance will be received as the `Proc` argument, and its output will be the callable argument. But if this option wasn't defined, the `Micro::Observers::Event` instance will be the callable argument.
|
232
|
+
4. `:context` will be the context data that was defined in the moment that you attach the observer.
|
223
233
|
|
224
234
|
```ruby
|
225
235
|
class Person
|
@@ -232,9 +242,7 @@ class Person
|
|
232
242
|
end
|
233
243
|
|
234
244
|
def name=(new_name)
|
235
|
-
observers.subject_changed(new_name != @name)
|
236
|
-
|
237
|
-
return unless observers.subject_changed?
|
245
|
+
return unless observers.subject_changed(new_name != @name)
|
238
246
|
|
239
247
|
@name = new_name
|
240
248
|
|
@@ -251,7 +259,8 @@ person = Person.new('Rodrigo')
|
|
251
259
|
person.observers.on(
|
252
260
|
event: :name_has_been_changed,
|
253
261
|
call: PrintPersonName,
|
254
|
-
with: -> event { {person: event.subject, number:
|
262
|
+
with: -> event { {person: event.subject, number: event.context} },
|
263
|
+
context: rand
|
255
264
|
)
|
256
265
|
|
257
266
|
person.name = 'Serradura'
|
@@ -264,7 +273,7 @@ person.name = 'Serradura'
|
|
264
273
|
### Calling the observers
|
265
274
|
|
266
275
|
You can use a callable (a class, module, or object that responds to the call method) to be your observers.
|
267
|
-
To do this, you only need make use of the method `#call` instead of `#notify`.
|
276
|
+
To do this, you only need to make use of the method `#call` instead of `#notify`.
|
268
277
|
|
269
278
|
```ruby
|
270
279
|
class Order
|
@@ -286,7 +295,7 @@ order.cancel!
|
|
286
295
|
# The order #(70196221441820) has been canceled.
|
287
296
|
```
|
288
297
|
|
289
|
-
> **Note**: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.
|
298
|
+
> **Note**: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.
|
290
299
|
|
291
300
|
[⬆️ Back to Top](#table-of-contents-)
|
292
301
|
|
@@ -300,7 +309,7 @@ If you use the methods `#notify!` or `#call!` you won't need to mark observers w
|
|
300
309
|
|
301
310
|
### ActiveRecord and ActiveModel integrations
|
302
311
|
|
303
|
-
To make use of this feature you need to require an additional module
|
312
|
+
To make use of this feature you need to require an additional module.
|
304
313
|
|
305
314
|
Gemfile example:
|
306
315
|
```ruby
|
@@ -312,13 +321,20 @@ This feature will expose modules that could be used to add macros (static method
|
|
312
321
|
|
313
322
|
#### notify_observers_on()
|
314
323
|
|
315
|
-
The `notify_observers_on` allows you to
|
324
|
+
The `notify_observers_on` allows you to define one or more `ActiveModel`/`ActiveRecord` callbacks, that will be used to notify your object observers.
|
316
325
|
|
317
326
|
```ruby
|
318
327
|
class Post < ActiveRecord::Base
|
319
328
|
include ::Micro::Observers::For::ActiveRecord
|
320
329
|
|
321
|
-
notify_observers_on(:after_commit) #
|
330
|
+
notify_observers_on(:after_commit) # using multiple callbacks. e.g. notify_observers_on(:before_save, :after_commit)
|
331
|
+
|
332
|
+
# The method above does the same as the commented example below.
|
333
|
+
#
|
334
|
+
# after_commit do |record|
|
335
|
+
# record.subject_changed!
|
336
|
+
# record.notify(:after_commit)
|
337
|
+
# end
|
322
338
|
end
|
323
339
|
|
324
340
|
module TitlePrinter
|
@@ -347,13 +363,20 @@ end
|
|
347
363
|
|
348
364
|
#### notify_observers()
|
349
365
|
|
350
|
-
The `notify_observers` allows you to
|
366
|
+
The `notify_observers` allows you to define one or more *events*, that will be used to notify after the execution of some `ActiveModel`/`ActiveRecord` callback.
|
351
367
|
|
352
368
|
```ruby
|
353
369
|
class Post < ActiveRecord::Base
|
354
370
|
include ::Micro::Observers::For::ActiveRecord
|
355
371
|
|
356
372
|
after_commit(¬ify_observers(:transaction_completed))
|
373
|
+
|
374
|
+
# The method above does the same as the commented example below.
|
375
|
+
#
|
376
|
+
# after_commit do |record|
|
377
|
+
# record.subject_changed!
|
378
|
+
# record.notify(:transaction_completed)
|
379
|
+
# end
|
357
380
|
end
|
358
381
|
|
359
382
|
module TitlePrinter
|
data/README.pt-BR.md
ADDED
@@ -0,0 +1,426 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<h1 align="center">👀 μ-observers</h1>
|
3
|
+
<p align="center"><i>Implementação simples e poderosa do padrão observer.</i></p>
|
4
|
+
<br>
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<p align="center">
|
8
|
+
<img src="https://img.shields.io/badge/ruby->%3D%202.2.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
9
|
+
|
10
|
+
<a href="https://rubygems.org/gems/u-observers">
|
11
|
+
<img alt="Gem" src="https://img.shields.io/gem/v/u-observers.svg?style=flat-square">
|
12
|
+
</a>
|
13
|
+
|
14
|
+
<a href="https://travis-ci.com/serradura/u-observers">
|
15
|
+
<img alt="Build Status" src="https://travis-ci.com/serradura/u-observers.svg?branch=main">
|
16
|
+
</a>
|
17
|
+
|
18
|
+
<a href="https://codeclimate.com/github/serradura/u-observers/maintainability">
|
19
|
+
<img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/e72ffa84bc95c59823f2/maintainability">
|
20
|
+
</a>
|
21
|
+
|
22
|
+
<a href="https://codeclimate.com/github/serradura/u-observers/test_coverage">
|
23
|
+
<img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/e72ffa84bc95c59823f2/test_coverage">
|
24
|
+
</a>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
Esta gem implementa o padrão observer[[1]](https://en.wikipedia.org/wiki/Observer_pattern)[[2]](https://refactoring.guru/design-patterns/observer) (também conhecido como publicar/assinar). Ela fornece um mecanismo simples para um objeto informar um conjunto de objetos de terceiros interessados quando seu estado muda.
|
28
|
+
|
29
|
+
A biblioteca padrão do Ruby [tem uma abstração](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html) que permite usar esse padrão, mas seu design pode entrar em conflito com outras bibliotecas convencionais, como [`ActiveModel`/`ActiveRecord`](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed), que também tem o método [`changed`](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html#method-i-changed). Nesse caso, o comportamento ficaria comprometido por conta dessa sobrescrita de métodos.
|
30
|
+
|
31
|
+
Por causa desse problema, decidi criar uma gem que encapsula o padrão sem alterar tanto a implementação do objeto. O `Micro::Observers` inclui apenas um método de instância na classe de destino (sua instância será o sujeito/objeto observado).
|
32
|
+
|
33
|
+
# Índice <!-- omit in toc -->
|
34
|
+
|
35
|
+
- [Instalação](#instalação)
|
36
|
+
- [Compatibilidade](#compatibilidade)
|
37
|
+
- [Uso](#uso)
|
38
|
+
- [Compartilhando um contexto com seus observadores](#compartilhando-um-contexto-com-seus-observadores)
|
39
|
+
- [Compartilhando dados ao notificar os observadores](#compartilhando-dados-ao-notificar-os-observadores)
|
40
|
+
- [O que é `Micro::Observers::Event`?](#o-que-é-microobserversevent)
|
41
|
+
- [Usando um calleable como um observador](#usando-um-calleable-como-um-observador)
|
42
|
+
- [Chamando os observadores](#chamando-os-observadores)
|
43
|
+
- [Notificar observadores sem marcá-los como alterados](#notificar-observadores-sem-marcá-los-como-alterados)
|
44
|
+
- [Integrações ActiveRecord e ActiveModel](#integrações-activerecord-e-activemodel)
|
45
|
+
- [notify_observers_on()](#notify_observers_on)
|
46
|
+
- [notify_observers()](#notify_observers)
|
47
|
+
- [Desenvolvimento](#desenvolvimento)
|
48
|
+
- [Contribuindo](#contribuindo)
|
49
|
+
- [License](#license)
|
50
|
+
- [Código de conduta](#código-de-conduta)
|
51
|
+
|
52
|
+
# Instalação
|
53
|
+
|
54
|
+
Adicione esta linha ao Gemfile da sua aplicação e execute `bundle install`:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
gem 'u-observers'
|
58
|
+
```
|
59
|
+
|
60
|
+
# Compatibilidade
|
61
|
+
|
62
|
+
| u-observers | branch | ruby | activerecord |
|
63
|
+
| ----------- | ------- | -------- | ------------- |
|
64
|
+
| unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
65
|
+
| 2.1.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
66
|
+
| 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
67
|
+
|
68
|
+
> **Nota**: O ActiveRecord não é uma dependência, mas você pode adicionar um módulo para habilitar alguns métodos estáticos que foram projetados para serem usados com seus [callbacks](https://guides.rubyonrails.org/active_record_callbacks.html).
|
69
|
+
|
70
|
+
[⬆️ Voltar para o índice](#índice-)
|
71
|
+
|
72
|
+
## Uso
|
73
|
+
|
74
|
+
Qualquer classe com o `Micro::Observers` incluído pode notificar eventos para observadores anexados.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'securerandom'
|
78
|
+
|
79
|
+
class Order
|
80
|
+
include Micro::Observers
|
81
|
+
|
82
|
+
attr_reader :code
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
@code, @status = SecureRandom.alphanumeric, :draft
|
86
|
+
end
|
87
|
+
|
88
|
+
def canceled?
|
89
|
+
@status == :canceled
|
90
|
+
end
|
91
|
+
|
92
|
+
def cancel!
|
93
|
+
return self if canceled?
|
94
|
+
|
95
|
+
@status = :canceled
|
96
|
+
|
97
|
+
observers.subject_changed!
|
98
|
+
observers.notify(:canceled) and return self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module OrderEvents
|
103
|
+
def self.canceled(order)
|
104
|
+
puts "The order #(#{order.code}) has been canceled."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
order = Order.new
|
109
|
+
#<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
|
110
|
+
|
111
|
+
order.observers.attach(OrderEvents) # anexando vários observadores. Exemplo: observers.attach(A, B, C)
|
112
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]>
|
113
|
+
|
114
|
+
order.canceled?
|
115
|
+
# false
|
116
|
+
|
117
|
+
order.cancel!
|
118
|
+
# A mensagem abaixo será impressa pelo observador (OrderEvents):
|
119
|
+
# The order #(X0o9yf1GsdQFvLR4) has been canceled
|
120
|
+
|
121
|
+
order.canceled?
|
122
|
+
# true
|
123
|
+
|
124
|
+
order.observers.detach(OrderEvents) # desanexando vários observadores. Exemplo: observers.detach(A, B, C)
|
125
|
+
# <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[]>
|
126
|
+
|
127
|
+
order.canceled?
|
128
|
+
# true
|
129
|
+
|
130
|
+
order.observers.subject_changed!
|
131
|
+
order.observers.notify(:canceled) # nada acontecerá, pois não há observadores vinculados (observers.attach)
|
132
|
+
```
|
133
|
+
|
134
|
+
**Destaques do exemplo anterior:**
|
135
|
+
|
136
|
+
Para evitar um comportamento indesejado, você precisa marcar o "subject" (sujeito) como alterado antes de notificar seus observadores sobre algum evento.
|
137
|
+
|
138
|
+
Você pode fazer isso ao usar o método `#subject_changed!`. Ele marcará automaticamente o sujeito como alterado.
|
139
|
+
|
140
|
+
Mas se você precisar aplicar alguma condicional para marcar uma mudança, você pode usar o método `#subject_changed`. Exemplo: `observers.subject_changed(name != new_name)`
|
141
|
+
|
142
|
+
O método `#notify` sempre requer um evento para fazer uma transmissão. Portanto, se você tentar usá-lo sem nenhum evento, você obterá uma exceção.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
order.observers.notify
|
146
|
+
# ArgumentError (no events (expected at least 1))
|
147
|
+
```
|
148
|
+
|
149
|
+
[⬆️ Voltar para o índice](#índice-)
|
150
|
+
|
151
|
+
### Compartilhando um contexto com seus observadores
|
152
|
+
|
153
|
+
Para compartilhar um valor de contexto (qualquer tipo de objeto Ruby) com um ou mais observadores, você precisará usar a palavra-chave `:context` como o último argumento do método `#attach`. Este recurso oferece a você uma oportunidade única de compartilhar um valor no momento de anexar um *observer*.
|
154
|
+
|
155
|
+
Quando o método do observer receber dois argumentos, o primeiro será o sujeito e o segundo uma instância `Micro::Observers::Event` que terá o valor do contexto.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class Order
|
159
|
+
include Micro::Observers
|
160
|
+
|
161
|
+
def cancel!
|
162
|
+
observers.subject_changed!
|
163
|
+
observers.notify(:canceled)
|
164
|
+
self
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module OrderEvents
|
169
|
+
def self.canceled(order, event)
|
170
|
+
puts "The order #(#{order.object_id}) has been canceled. (from: #{event.context[:from]})" # event.ctx é um alias para event.context
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
order = Order.new
|
175
|
+
order.observers.attach(OrderEvents, context: { from: 'example #2' }) # anexando vários observadores. Exemplo: observers.attach(A, B, context: {hello:: world})
|
176
|
+
order.cancel!
|
177
|
+
# A mensagem abaixo será impressa pelo observador (OrderEvents):
|
178
|
+
# The order #(70196221441820) has been canceled. (from: example #2)
|
179
|
+
```
|
180
|
+
|
181
|
+
[⬆️ Voltar para o índice](#índice-)
|
182
|
+
|
183
|
+
### Compartilhando dados ao notificar os observadores
|
184
|
+
|
185
|
+
Como mencionado anteriormente, o [`event context`](#compartilhando-um-contexto-com-seus-observadores) é um valor armazenado quando você anexa seu *observer*. Mas, às vezes, será útil enviar alguns dados adicionais ao transmitir um evento aos seus *observers*. O `event data` dá a você esta oportunidade única de compartilhar algum valor no momento da notificação.
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
class Order
|
189
|
+
include Micro::Observers
|
190
|
+
end
|
191
|
+
|
192
|
+
module OrderHandler
|
193
|
+
def self.changed(order, event)
|
194
|
+
puts "The order #(#{order.object_id}) received the number #{event.data} from #{event.ctx[:from]}."
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
order = Order.new
|
199
|
+
order.observers.attach(OrderHandler, context: { from: 'example #3' })
|
200
|
+
order.observers.subject_changed!
|
201
|
+
order.observers.notify(:changed, data: 1)
|
202
|
+
|
203
|
+
# A mensagem abaixo será impressa pelo observador (OrderHandler):
|
204
|
+
# The order #(70196221441820) received the number 1 from example #3.
|
205
|
+
```
|
206
|
+
|
207
|
+
[⬆️ Voltar para o índice](#índice-)
|
208
|
+
|
209
|
+
### O que é `Micro::Observers::Event`?
|
210
|
+
|
211
|
+
O `Micro::Observers::Event` é o payload do evento. Veja abaixo todas as suas propriedades:
|
212
|
+
- `#name` será o evento transmitido.
|
213
|
+
- `#subject` será o sujeito observado.
|
214
|
+
- `#context` serão [os dados de contexto](#compartilhando-um-contexto-com-seus-observadores) que foram definidos no momento em que você anexa o *observer*.
|
215
|
+
- `#data` será [o valor compartilhado na notificação dos observadores](#compartilhando-dados-ao-notificar-os-observadores).
|
216
|
+
- `#ctx` é um apelido para o método `#context`.
|
217
|
+
- `#subj` é um *alias* para o método `#subject`.
|
218
|
+
|
219
|
+
[⬆️ Voltar para o índice](#índice-)
|
220
|
+
|
221
|
+
### Usando um calleable como um observador
|
222
|
+
|
223
|
+
O método `observers.on()` permite que você anexe um callable (objeto que responda ao método `call`) como um observador.
|
224
|
+
|
225
|
+
Um callable tende a ter uma responsabilidade bem definida, promovendo assim o uso de [SRP (Single-responsibility principle).
|
226
|
+
|
227
|
+
Normalmente, um callable tem uma responsabilidade bem definida (faz apenas uma coisa), por isso, tende a ser mais amigável com o [SRP (princípio de responsabilidade única)](https://en.wikipedia.org/wiki/Single-responsibility_principle) do que um observador convencional (que poderia ter N métodos para responder a diferentes tipos de notificação).
|
228
|
+
|
229
|
+
Este método recebe as opções abaixo:
|
230
|
+
1. `:event` o nome do evento esperado.
|
231
|
+
2. `:call` o próprio callable.
|
232
|
+
3. `:with` (opcional) pode definir o valor que será usado como argumento do objeto callable. Portanto, se for um `Proc`, uma instância de `Micro::Observers::Event` será recebida como o argumento `Proc` e sua saída será o argumento que pode ser chamado. Mas se essa opção não for definida, a instância `Micro::Observers::Event` será o argumento do callable.
|
233
|
+
4. `#context` serão os dados de contexto que foram definidos no momento em que você anexa o *observer*.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class Person
|
237
|
+
include Micro::Observers
|
238
|
+
|
239
|
+
attr_reader :name
|
240
|
+
|
241
|
+
def initialize(name)
|
242
|
+
@name = name
|
243
|
+
end
|
244
|
+
|
245
|
+
def name=(new_name)
|
246
|
+
return unless observers.subject_changed(new_name != @name)
|
247
|
+
|
248
|
+
@name = new_name
|
249
|
+
|
250
|
+
observers.notify(:name_has_been_changed)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
PrintPersonName = -> (data) do
|
255
|
+
puts("Person name: #{data.fetch(:person).name}, number: #{data.fetch(:number)}")
|
256
|
+
end
|
257
|
+
|
258
|
+
person = Person.new('Aristóteles')
|
259
|
+
|
260
|
+
person.observers.on(
|
261
|
+
event: :name_has_been_changed,
|
262
|
+
call: PrintPersonName,
|
263
|
+
with: -> event { {person: event.subject, number: rand} }
|
264
|
+
)
|
265
|
+
|
266
|
+
person.name = 'Coutinho'
|
267
|
+
|
268
|
+
# A mensagem abaixo será impressa pelo observador (PrintPersonName):
|
269
|
+
# Person name: Coutinho, number: 0.5018509191706862
|
270
|
+
```
|
271
|
+
|
272
|
+
[⬆️ Voltar para o índice](#índice-)
|
273
|
+
|
274
|
+
### Chamando os observadores
|
275
|
+
|
276
|
+
Você pode usar um callable (uma classe, módulo ou objeto que responda ao método `call`) para ser seu *observer*. Para fazer isso, você só precisa usar o método `#call` em vez de `#notify`.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
class Order
|
280
|
+
include Micro::Observers
|
281
|
+
|
282
|
+
def cancel!
|
283
|
+
observers.subject_changed!
|
284
|
+
observers.call # na prática, este é um alias para observers.notify(:call)
|
285
|
+
self
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
OrderCancellation = -> (order) { puts "The order #(#{order.object_id}) has been canceled." }
|
290
|
+
|
291
|
+
order = Order.new
|
292
|
+
order.observers.attach(OrderCancellation)
|
293
|
+
order.cancel!
|
294
|
+
|
295
|
+
# A mensagem abaixo será impressa pelo observador (OrderCancellation):
|
296
|
+
# The order #(70196221441820) has been canceled.
|
297
|
+
```
|
298
|
+
|
299
|
+
> **Nota**: O `observers.call` pode receber um ou mais eventos, mas no caso de receber eventos/argumentos, o evento padrão (`call`) não será transmitido.
|
300
|
+
|
301
|
+
[⬆️ Voltar para o índice](#índice-)
|
302
|
+
|
303
|
+
### Notificar observadores sem marcá-los como alterados
|
304
|
+
|
305
|
+
Este recurso deve ser usado com cuidado!
|
306
|
+
|
307
|
+
Se você usar os métodos `#notify!` ou `#call!` você não precisará marcar observers com `#subject_changed`.
|
308
|
+
|
309
|
+
[⬆️ Voltar para o índice](#índice-)
|
310
|
+
|
311
|
+
### Integrações ActiveRecord e ActiveModel
|
312
|
+
|
313
|
+
Para fazer uso deste recurso, você precisa de um módulo adicional.
|
314
|
+
|
315
|
+
Exemplo de Gemfile:
|
316
|
+
```ruby
|
317
|
+
gem 'u-observers', require: 'u-observers/for/active_record'
|
318
|
+
```
|
319
|
+
|
320
|
+
Este recurso irá expor módulos que podem ser usados para adicionar macros (métodos estáticos) que foram projetados para funcionar com os callbacks do `ActiveModel`/`ActiveRecord`. Exemplo:
|
321
|
+
|
322
|
+
#### notify_observers_on()
|
323
|
+
|
324
|
+
O `notify_observers_on` permite que você defina um ou mais callbacks do `ActiveModel`/`ActiveRecord`, que serão usados para notificar seus *observers*.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
class Post < ActiveRecord::Base
|
328
|
+
include ::Micro::Observers::For::ActiveRecord
|
329
|
+
|
330
|
+
notify_observers_on(:after_commit) # usando vários callbacks. Exemplo: notificar_observadores_on(:before_save, :after_commit)
|
331
|
+
|
332
|
+
# O método acima faz o mesmo que o exemplo comentado abaixo.
|
333
|
+
#
|
334
|
+
# after_commit do | record |
|
335
|
+
# record.subject_changed!
|
336
|
+
# record.notify (:after_commit)
|
337
|
+
# end
|
338
|
+
end
|
339
|
+
|
340
|
+
module TitlePrinter
|
341
|
+
def self.after_commit(post)
|
342
|
+
puts "Title: #{post.title}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
module TitlePrinterWithContext
|
347
|
+
def self.after_commit(post, event)
|
348
|
+
puts "Title: #{post.title} (from: #{event.context[:from]})"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
Post.transaction do
|
353
|
+
post = Post.new(title: 'Hello world')
|
354
|
+
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #6' })
|
355
|
+
post.save
|
356
|
+
end
|
357
|
+
|
358
|
+
# A mensagem abaixo será impressa pelos observadores (TitlePrinter, TitlePrinterWithContext):
|
359
|
+
# Title: Hello world
|
360
|
+
# Title: Hello world (de: exemplo # 6)
|
361
|
+
```
|
362
|
+
|
363
|
+
[⬆️ Voltar para o índice](#índice-)
|
364
|
+
|
365
|
+
#### notify_observers()
|
366
|
+
|
367
|
+
O `notify_observers` permite definir um ou mais eventos, que serão utilizados para notificar após a execução de algum callback do `ActiveModel`/`ActiveRecord`.
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
class Post < ActiveRecord::Base
|
371
|
+
include ::Micro::Observers::For::ActiveRecord
|
372
|
+
|
373
|
+
after_commit(¬ify_observers(:transaction_completed))
|
374
|
+
|
375
|
+
# O método acima faz o mesmo que o exemplo comentado abaixo.
|
376
|
+
#
|
377
|
+
# after_commit do | record |
|
378
|
+
# record.subject_changed!
|
379
|
+
# record.notify (:transaction_completed)
|
380
|
+
# end
|
381
|
+
end
|
382
|
+
|
383
|
+
module TitlePrinter
|
384
|
+
def self.transaction_completed(post)
|
385
|
+
puts("Title: #{post.title}")
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
module TitlePrinterWithContext
|
390
|
+
def self.transaction_completed(post, event)
|
391
|
+
puts("Title: #{post.title} (from: #{event.ctx[:from]})")
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
Post.transaction do
|
396
|
+
post = Post.new(title: 'Olá mundo')
|
397
|
+
post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #7' })
|
398
|
+
post.save
|
399
|
+
end
|
400
|
+
|
401
|
+
# A mensagem abaixo será impressa pelos observadores (TitlePrinter, TitlePrinterWithContext):
|
402
|
+
# Title: Olá mundo
|
403
|
+
# Title: Olá mundo (from: example # 5)
|
404
|
+
```
|
405
|
+
|
406
|
+
> **Observação**: você pode usar `include ::Micro::Observers::For::ActiveModel` se sua classe apenas fizer uso do `ActiveModel` e todos os exemplos anteriores funcionarão.
|
407
|
+
|
408
|
+
[⬆️ Voltar para o índice](#índice-)
|
409
|
+
|
410
|
+
## Desenvolvimento
|
411
|
+
|
412
|
+
Depois de verificar o repositório, execute `bin/setup` para instalar as dependências. Em seguida, execute `rake test` para executar os testes. Você também pode executar `bin/console` um prompt interativo que permitirá que você experimente.
|
413
|
+
|
414
|
+
Para instalar esta gem em sua máquina local, execute `bundle exec rake install`. Para lançar uma nova versão, atualize o número da versão em `version.rb` e execute `bundle exec rake release`, que criará uma tag git para a versão, envie os commits ao git e envie e envie o arquivo `.gem` para [rubygems.org](https://rubygems.org).
|
415
|
+
|
416
|
+
## Contribuindo
|
417
|
+
|
418
|
+
Reportar bugs e solicitações de pull-requests são bem-vindos no GitHub em https://github.com/serradura/u-observers. Este projeto pretende ser um espaço seguro e acolhedor para colaboração, e espera-se que os colaboradores sigam o [código de conduta](https://github.com/serradura/u-observers/blob/master/CODE_OF_CONDUCT.md).
|
419
|
+
|
420
|
+
## License
|
421
|
+
|
422
|
+
A gem está disponível como código aberto sob os termos da [Licença MIT](https://opensource.org/licenses/MIT).
|
423
|
+
|
424
|
+
## Código de conduta
|
425
|
+
|
426
|
+
Espera-se que todos que interagem nas bases de código do projeto `Micro::Observers`, rastreadores de problemas, salas de bate-papo e listas de discussão sigam o [código de conduta](https://github.com/serradura/u-observers/blob/master/CODE_OF_CONDUCT.md).
|
@@ -4,8 +4,10 @@ module Micro
|
|
4
4
|
module Observers
|
5
5
|
|
6
6
|
class Event::Names
|
7
|
-
|
8
|
-
|
7
|
+
EMPTY_ARRAY = [].freeze
|
8
|
+
|
9
|
+
def self.[](value, default: EMPTY_ARRAY)
|
10
|
+
values = Utils::Arrays.flatten_and_compact(value)
|
9
11
|
|
10
12
|
values.empty? ? default : values
|
11
13
|
end
|
@@ -18,7 +18,7 @@ module Micro
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notify_observers_on(*callback_methods)
|
21
|
-
Utils.
|
21
|
+
Utils::Arrays.flatten_and_compact(callback_methods).each do |callback_method|
|
22
22
|
self.public_send(callback_method, ¬ify_observers!([callback_method]))
|
23
23
|
end
|
24
24
|
end
|
data/lib/micro/observers/set.rb
CHANGED
@@ -4,11 +4,13 @@ module Micro
|
|
4
4
|
module Observers
|
5
5
|
|
6
6
|
class Set
|
7
|
+
EMPTY_HASH = {}.freeze
|
8
|
+
|
7
9
|
MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
|
8
10
|
|
9
11
|
MapSubscribers = -> (value) do
|
10
|
-
array = Utils.
|
11
|
-
array.map { |observer| MapSubscriber[observer,
|
12
|
+
array = Utils::Arrays.flatten_and_compact(value.kind_of?(Array) ? value : [])
|
13
|
+
array.map { |observer| MapSubscriber[observer, EMPTY_HASH] }
|
12
14
|
end
|
13
15
|
|
14
16
|
GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
|
@@ -60,9 +62,9 @@ module Micro
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def attach(*args)
|
63
|
-
options = args.last.is_a?(Hash) ? args.pop :
|
65
|
+
options = args.last.is_a?(Hash) ? args.pop : EMPTY_HASH
|
64
66
|
|
65
|
-
Utils.
|
67
|
+
Utils::Arrays.flatten_and_compact(args).each do |observer|
|
66
68
|
@subscribers << MapSubscriber[observer, options] unless included?(observer)
|
67
69
|
end
|
68
70
|
|
@@ -70,19 +72,19 @@ module Micro
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def detach(*args)
|
73
|
-
Utils.
|
75
|
+
Utils::Arrays.flatten_and_compact(args).each do |observer|
|
74
76
|
@subscribers.delete_if(&EqualTo[observer])
|
75
77
|
end
|
76
78
|
|
77
79
|
self
|
78
80
|
end
|
79
81
|
|
80
|
-
def on(options =
|
81
|
-
event, callable, with = options[:event], options[:call], options[:with]
|
82
|
+
def on(options = EMPTY_HASH)
|
83
|
+
event, callable, with, context = options[:event], options[:call], options[:with], options[:context]
|
82
84
|
|
83
85
|
return self unless event.is_a?(Symbol) && callable.respond_to?(:call)
|
84
86
|
|
85
|
-
@subscribers << [:callable, event, [callable, with]] unless included?(callable)
|
87
|
+
@subscribers << [:callable, event, [callable, with, context]] unless included?(callable)
|
86
88
|
|
87
89
|
self
|
88
90
|
end
|
@@ -152,15 +154,15 @@ module Micro
|
|
152
154
|
handler.call(@subject, Event.new(event_name, @subject, context, data))
|
153
155
|
end
|
154
156
|
|
155
|
-
def notify_callable(expected_event_name, event_name,
|
157
|
+
def notify_callable(expected_event_name, event_name, opt, data)
|
156
158
|
return if expected_event_name != event_name
|
157
159
|
|
158
|
-
callable, with =
|
160
|
+
callable, with, context = opt[0], opt[1], opt[2]
|
159
161
|
callable_arg =
|
160
162
|
if with && !with.is_a?(Proc)
|
161
163
|
with
|
162
164
|
else
|
163
|
-
event = Event.new(event_name, @subject,
|
165
|
+
event = Event.new(event_name, @subject, context, data)
|
164
166
|
|
165
167
|
with.is_a?(Proc) ? with.call(event) : event
|
166
168
|
end
|
@@ -168,7 +170,7 @@ module Micro
|
|
168
170
|
callable.call(callable_arg)
|
169
171
|
end
|
170
172
|
|
171
|
-
private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
|
173
|
+
private_constant :EMPTY_HASH, :INVALID_BOOLEAN_MSG, :CALL_EVENT
|
172
174
|
private_constant :MapSubscriber, :MapSubscribers, :GetObserver, :EqualTo
|
173
175
|
end
|
174
176
|
|
@@ -4,11 +4,10 @@ module Micro
|
|
4
4
|
module Observers
|
5
5
|
|
6
6
|
module Utils
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
Array(value).flatten.tap(&:compact!)
|
7
|
+
module Arrays
|
8
|
+
def self.flatten_and_compact(value)
|
9
|
+
Array(value).flatten.tap(&:compact!)
|
10
|
+
end
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
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: 2.
|
4
|
+
version: 2.1.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-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,6 +53,7 @@ files:
|
|
53
53
|
- Gemfile
|
54
54
|
- LICENSE.txt
|
55
55
|
- README.md
|
56
|
+
- README.pt-BR.md
|
56
57
|
- Rakefile
|
57
58
|
- bin/console
|
58
59
|
- bin/setup
|