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 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