u-observers 2.0.0 → 2.1.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 +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
|