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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5098d44090f611866c96aa4005c6475742972a9a986dbbcd5618753932b33817
4
- data.tar.gz: 8b18f175cc5ff7fd1aae8e34302be62a1f295a151231c62e253d4b984d0225b8
3
+ metadata.gz: 52f8cf4c9f76af4b9438412dc60fa7b4156fa84afec92f89a12bb40b90d1ed32
4
+ data.tar.gz: 1c1ad202a47f467e468186b58671bed796656180660302f057005a7387bdcc7f
5
5
  SHA512:
6
- metadata.gz: e51002b6af05c7aba17eff30ba962d1d726ce9e169ae19c8a71626c035daf7c1e4e6da727c74f73c06c46ad0f5b1faaad1d1248df018ada97aa123a613e1dedf
7
- data.tar.gz: 20972cd57db02b9a25905896af9429a597cfb22e951fe73a3af6346962cd1f3574df30c241b7aa9ef3a99c502097ad0a7d75210a33227a98c6d3435371c8bb03
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 been compromised.
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
- - [Passing a context for your observers](#passing-a-context-for-your-observers)
38
- - [Passing data when performing observers](#passing-data-when-performing-observers)
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
- - [Passing a callable as an observer](#passing-a-callable-as-an-observer)
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
- | 2.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
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, do you need to mark the subject as changed before notify your observers about some event.
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
  [⬆️ &nbsp; Back to Top](#table-of-contents-)
148
151
 
149
- ### Passing a context for your observers
152
+ ### Sharing a context with your observers
150
153
 
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.
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
  [⬆️ &nbsp; Back to Top](#table-of-contents-)
180
183
 
181
- ### Passing data when performing observers
184
+ ### Sharing data when notifying the observers
182
185
 
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.
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](#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).
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
  [⬆️ &nbsp; Back to Top](#table-of-contents-)
216
221
 
217
- ### Passing a callable as an observer
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
- 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.
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: rand} }
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.a
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
  [⬆️ &nbsp; 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 (`require 'u-observers/for/active_record'`).
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 pass one or more `ActiveModel`/`ActiveRecord` callbacks, that will be used to notify your object observers.
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) # passing multiple callbacks. e.g. notify_observers_on(:before_save, :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 pass one or more *events*, that will be used to notify after the execution of some `ActiveModel`/`ActiveRecord` callback.
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(&notify_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
@@ -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(&notify_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
- def self.[](value, default: Utils::EMPTY_ARRAY)
8
- values = Utils.compact_array(value)
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.compact_array(callback_methods).each do |callback_method|
21
+ Utils::Arrays.flatten_and_compact(callback_methods).each do |callback_method|
22
22
  self.public_send(callback_method, &notify_observers!([callback_method]))
23
23
  end
24
24
  end
@@ -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.compact_array(value.kind_of?(Array) ? value : [])
11
- array.map { |observer| MapSubscriber[observer, Utils::EMPTY_HASH] }
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 : Utils::EMPTY_HASH
65
+ options = args.last.is_a?(Hash) ? args.pop : EMPTY_HASH
64
66
 
65
- Utils.compact_array(args).each do |observer|
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.compact_array(args).each do |observer|
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 = Utils::EMPTY_HASH)
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, context, data)
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 = context[0], context[1]
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, nil, data)
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
- EMPTY_HASH = {}.freeze
8
- EMPTY_ARRAY = [].freeze
9
-
10
- def self.compact_array(value)
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
 
@@ -1,5 +1,5 @@
1
1
  module Micro
2
2
  module Observers
3
- VERSION = '2.0.0'
3
+ VERSION = '2.1.0'
4
4
  end
5
5
  end
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.0.0
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-06 00:00:00.000000000 Z
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