u-observers 2.1.0 → 2.2.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: 52f8cf4c9f76af4b9438412dc60fa7b4156fa84afec92f89a12bb40b90d1ed32
4
- data.tar.gz: 1c1ad202a47f467e468186b58671bed796656180660302f057005a7387bdcc7f
3
+ metadata.gz: e7da28034b714bb64baf033cf43705ed585fa007659964dc36bec440436292f0
4
+ data.tar.gz: cd2b40e92cdfd12708ab36f1ed8a7726ecfbc2c6e96223ca090aedc7758f44f8
5
5
  SHA512:
6
- metadata.gz: c2f779070c6e11ef08c71b85f9d90342907cef3e910e7f6a7b61bbebeeec283baf56d197df1edf3bb4bb7437424e43d74208d97fed1e745b76367b2c14fb2206
7
- data.tar.gz: 7995b96fe8868e126fd053e05e831b92adc421325b133324222591c74a288ba4de9677dc59b2b5f57a8d8a437c04a342f04a9548ed460546e9f56b571e4ac3c5
6
+ metadata.gz: 51be920a8f6f0123e0a94fbf180fb85517ddccb107f729f6eaa38ac840e14a9d207cb82e8f3f76e018e3836f4a7cc0b47b7ea6320ffd0615d22df49c6b693d73
7
+ data.tar.gz: 66c73487b724149f640e096b24a1e9ecf5e5d8780e9679f1d182823eda377e8b289ed88c98d73f6a81147e945eba7a8ce24278244ab50c1a191ebba1b912e265
data/README.md CHANGED
@@ -42,6 +42,10 @@ Because of this issue, I decided to create a gem that encapsulates the pattern w
42
42
  - [Using a callable as an observer](#using-a-callable-as-an-observer)
43
43
  - [Calling the observers](#calling-the-observers)
44
44
  - [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
45
+ - [Defining observers that execute only once](#defining-observers-that-execute-only-once)
46
+ - [`observers.attach(*args, perform_once: true)`](#observersattachargs-perform_once-true)
47
+ - [`observers.once(event:, call:, ...)`](#observersonceevent-call-)
48
+ - [Detaching observers](#detaching-observers)
45
49
  - [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
46
50
  - [notify_observers_on()](#notify_observers_on)
47
51
  - [notify_observers()](#notify_observers)
@@ -63,7 +67,7 @@ gem 'u-observers'
63
67
  | u-observers | branch | ruby | activerecord |
64
68
  | ----------- | ------- | -------- | ------------- |
65
69
  | unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
66
- | 2.1.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
70
+ | 2.2.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
67
71
  | 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
68
72
 
69
73
  > **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).
@@ -109,7 +113,7 @@ end
109
113
  order = Order.new
110
114
  #<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
111
115
 
112
- order.observers.attach(OrderEvents) # attaching multiple observers. e.g. observers.attach(A, B, C)
116
+ order.observers.attach(OrderEvents) # attaching multiple observers. e.g. observers.attach(A, B, C)
113
117
  # <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]>
114
118
 
115
119
  order.canceled?
@@ -122,7 +126,7 @@ order.cancel!
122
126
  order.canceled?
123
127
  # true
124
128
 
125
- order.observers.detach(OrderEvents) # detaching multiple observers. e.g. observers.detach(A, B, C)
129
+ order.observers.detach(OrderEvents) # detaching multiple observers. e.g. observers.detach(A, B, C)
126
130
  # <#Micro::Observers::Set @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[]>
127
131
 
128
132
  order.canceled?
@@ -286,12 +290,12 @@ class Order
286
290
  end
287
291
  end
288
292
 
289
- OrderCancellation = -> (order) { puts "The order #(#{order.object_id}) has been canceled." }
293
+ NotifyAfterCancel = -> (order) { puts "The order #(#{order.object_id}) has been canceled." }
290
294
 
291
295
  order = Order.new
292
- order.observers.attach(OrderCancellation)
296
+ order.observers.attach(NotifyAfterCancel)
293
297
  order.cancel!
294
- # The message below will be printed by the observer (OrderCancellation):
298
+ # The message below will be printed by the observer (NotifyAfterCancel):
295
299
  # The order #(70196221441820) has been canceled.
296
300
  ```
297
301
 
@@ -307,6 +311,106 @@ If you use the methods `#notify!` or `#call!` you won't need to mark observers w
307
311
 
308
312
  [⬆️ &nbsp; Back to Top](#table-of-contents-)
309
313
 
314
+ ### Defining observers that execute only once
315
+
316
+ There are two ways to attach an observer and define it to be performed only once.
317
+
318
+ The first way to do this is passing the `perform_once: true` option to the `observers.attach()` method. e.g.
319
+
320
+ #### `observers.attach(*args, perform_once: true)`
321
+
322
+ ```ruby
323
+ class Order
324
+ include Micro::Observers
325
+
326
+ def cancel!
327
+ observers.notify!(:canceled)
328
+ end
329
+ end
330
+
331
+ module OrderNotifications
332
+ def self.canceled(order)
333
+ puts "The order #(#{order.object_id}) has been canceled."
334
+ end
335
+ end
336
+
337
+ order = Order.new
338
+ order.observers.attach(OrderNotifications, perform_once: true) # you can also pass an array of observers with this option
339
+
340
+ order.observers.some? # true
341
+ order.cancel! # The order #(70291642071660) has been canceled.
342
+
343
+ order.observers.some? # false
344
+ order.cancel! # Nothing will happen because there aren't observers.
345
+ ```
346
+
347
+ #### `observers.once(event:, call:, ...)`
348
+
349
+ The second way to achieve this is using `observers.once()` that has the same API of [`observers.on()`](#using-a-callable-as-an-observer). But the difference of the `#once()` method is that it will remove the observer after its execution.
350
+
351
+ ```ruby
352
+ class Order
353
+ include Micro::Observers
354
+
355
+ def cancel!
356
+ observers.notify!(:canceled)
357
+ end
358
+ end
359
+
360
+ module NotifyAfterCancel
361
+ def self.call(event)
362
+ puts "The order #(#{event.subject.object_id}) has been canceled."
363
+ end
364
+ end
365
+
366
+ order = Order.new
367
+ order.observers.once(event: :canceled, call: NotifyAfterCancel)
368
+
369
+ order.observers.some? # true
370
+ order.cancel! # The order #(70301497466060) has been canceled.
371
+
372
+ order.observers.some? # false
373
+ order.cancel! # Nothing will happen because there aren't observers.
374
+ ```
375
+
376
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
377
+
378
+ ### Detaching observers
379
+
380
+ As shown in the first example, you can use the `observers.detach()` to remove observers.
381
+
382
+ But, there is an alternative method to remove observer objects or remove callables by their event names. The method to do this is: `observers.off()`.
383
+
384
+ ```ruby
385
+ class Order
386
+ include Micro::Observers
387
+ end
388
+
389
+ NotifyAfterCancel = -> {}
390
+
391
+ module OrderNotifications
392
+ def self.canceled(_order)
393
+ end
394
+ end
395
+
396
+ order = Order.new
397
+ order.observers.on(event: :canceled, call: NotifyAfterCancel)
398
+ order.observers.attach(OrderNotifications)
399
+
400
+ order.observers.some? # true
401
+ order.observers.count # 2
402
+
403
+ order.observers.off(:canceled) # removing the callable (NotifyAfterCancel).
404
+ order.observers.some? # true
405
+ order.observers.count # 1
406
+
407
+ order.observers.off(OrderNotifications)
408
+ order.observers.some? # false
409
+ order.observers.count # 0
410
+ ```
411
+
412
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
413
+
310
414
  ### ActiveRecord and ActiveModel integrations
311
415
 
312
416
  To make use of this feature you need to require an additional module.
@@ -318,7 +422,6 @@ gem 'u-observers', require: 'u-observers/for/active_record'
318
422
 
319
423
  This feature will expose modules that could be used to add macros (static methods) that were designed to work with `ActiveModel`/`ActiveRecord` callbacks. e.g:
320
424
 
321
-
322
425
  #### notify_observers_on()
323
426
 
324
427
  The `notify_observers_on` allows you to define one or more `ActiveModel`/`ActiveRecord` callbacks, that will be used to notify your object observers.
@@ -38,9 +38,13 @@ Por causa desse problema, decidi criar uma gem que encapsula o padrão sem alter
38
38
  - [Compartilhando um contexto com seus observadores](#compartilhando-um-contexto-com-seus-observadores)
39
39
  - [Compartilhando dados ao notificar os observadores](#compartilhando-dados-ao-notificar-os-observadores)
40
40
  - [O que é `Micro::Observers::Event`?](#o-que-é-microobserversevent)
41
- - [Usando um calleable como um observador](#usando-um-calleable-como-um-observador)
41
+ - [Usando um callable como um observador](#usando-um-callable-como-um-observador)
42
42
  - [Chamando os observadores](#chamando-os-observadores)
43
43
  - [Notificar observadores sem marcá-los como alterados](#notificar-observadores-sem-marcá-los-como-alterados)
44
+ - [Definindo observers que executam apenas uma vez](#definindo-observers-que-executam-apenas-uma-vez)
45
+ - [`observers.attach(*args, perform_once: true)`](#observersattachargs-perform_once-true)
46
+ - [`observers.once(event:, call:, ...)`](#observersonceevent-call-)
47
+ - [Desanexando observers](#desanexando-observers)
44
48
  - [Integrações ActiveRecord e ActiveModel](#integrações-activerecord-e-activemodel)
45
49
  - [notify_observers_on()](#notify_observers_on)
46
50
  - [notify_observers()](#notify_observers)
@@ -62,7 +66,7 @@ gem 'u-observers'
62
66
  | u-observers | branch | ruby | activerecord |
63
67
  | ----------- | ------- | -------- | ------------- |
64
68
  | unreleased | main | >= 2.2.0 | >= 3.2, < 6.1 |
65
- | 2.1.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
69
+ | 2.2.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
66
70
  | 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
67
71
 
68
72
  > **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).
@@ -218,19 +222,17 @@ O `Micro::Observers::Event` é o payload do evento. Veja abaixo todas as suas pr
218
222
 
219
223
  [⬆️ Voltar para o índice](#índice-)
220
224
 
221
- ### Usando um calleable como um observador
225
+ ### Usando um callable como um observador
222
226
 
223
227
  O método `observers.on()` permite que você anexe um callable (objeto que responda ao método `call`) como um observador.
224
228
 
225
- Um callable tende a ter uma responsabilidade bem definida, promovendo assim o uso de [SRP (Single-responsibility principle).
226
-
227
229
  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
230
 
229
231
  Este método recebe as opções abaixo:
230
232
  1. `:event` o nome do evento esperado.
231
233
  2. `:call` o próprio callable.
232
234
  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*.
235
+ 4. `:context` serão os dados de contexto que foram definidos no momento em que você anexa o *observer*.
234
236
 
235
237
  ```ruby
236
238
  class Person
@@ -260,7 +262,8 @@ person = Person.new('Aristóteles')
260
262
  person.observers.on(
261
263
  event: :name_has_been_changed,
262
264
  call: PrintPersonName,
263
- with: -> event { {person: event.subject, number: rand} }
265
+ with: -> event { {person: event.subject, number: event.context} },
266
+ context: rand
264
267
  )
265
268
 
266
269
  person.name = 'Coutinho'
@@ -308,6 +311,106 @@ Se você usar os métodos `#notify!` ou `#call!` você não precisará marcar ob
308
311
 
309
312
  [⬆️ Voltar para o índice](#índice-)
310
313
 
314
+ ### Definindo observers que executam apenas uma vez
315
+
316
+ Existem duas formas de anexar um observer e definir que ele executará apenas uma vez.
317
+
318
+ A primeira forma de fazer isso é passando a opção `perform_once: true` para o método `observers.attach()`. Exemplo:
319
+
320
+ #### `observers.attach(*args, perform_once: true)`
321
+
322
+ ```ruby
323
+ class Order
324
+ include Micro::Observers
325
+
326
+ def cancel!
327
+ observers.notify!(:canceled)
328
+ end
329
+ end
330
+
331
+ module OrderNotifications
332
+ def self.canceled(order)
333
+ puts "The order #(#{order.object_id}) has been canceled."
334
+ end
335
+ end
336
+
337
+ order = Order.new
338
+ order.observers.attach(OrderNotifications, perform_once: true) # you can also pass an array of observers with this option
339
+
340
+ order.observers.some? # true
341
+ order.cancel! # The order #(70291642071660) has been canceled.
342
+
343
+ order.observers.some? # false
344
+ order.cancel! # Nothing will happen because there aren't observers.
345
+ ```
346
+
347
+ #### `observers.once(event:, call:, ...)`
348
+
349
+ A segunda forma de conseguir isso é usando o método `observers.once()` que tem a mesma API do [`observers.on()`](#usando-um-callable-como-um-observador). Mas a diferença é que o método `#once()` removerá o observer após a sua execução.
350
+
351
+ ```ruby
352
+ class Order
353
+ include Micro::Observers
354
+
355
+ def cancel!
356
+ observers.notify!(:canceled)
357
+ end
358
+ end
359
+
360
+ module NotifyAfterCancel
361
+ def self.call(event)
362
+ puts "The order #(#{event.subject.object_id}) has been canceled."
363
+ end
364
+ end
365
+
366
+ order = Order.new
367
+ order.observers.once(event: :canceled, call: NotifyAfterCancel)
368
+
369
+ order.observers.some? # true
370
+ order.cancel! # The order #(70301497466060) has been canceled.
371
+
372
+ order.observers.some? # false
373
+ order.cancel! # Nothing will happen because there aren't observers.
374
+ ```
375
+
376
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
377
+
378
+ ### Desanexando observers
379
+
380
+ Como mostrado no primeiro exemplo, você pode usar o `observers.detach()` para remove observers.
381
+
382
+ Mas, existe uma alternativa a esse método que permite remover objetos observers ou remover callables pelo nome de seus eventos. O método para fazer isso é: `observers.off()`.
383
+
384
+ ```ruby
385
+ class Order
386
+ include Micro::Observers
387
+ end
388
+
389
+ NotifyAfterCancel = -> {}
390
+
391
+ module OrderNotifications
392
+ def self.canceled(_order)
393
+ end
394
+ end
395
+
396
+ order = Order.new
397
+ order.observers.on(event: :canceled, call: NotifyAfterCancel)
398
+ order.observers.attach(OrderNotifications)
399
+
400
+ order.observers.some? # true
401
+ order.observers.count # 2
402
+
403
+ order.observers.off(:canceled) # removing the callable (NotifyAfterCancel).
404
+ order.observers.some? # true
405
+ order.observers.count # 1
406
+
407
+ order.observers.off(OrderNotifications)
408
+ order.observers.some? # false
409
+ order.observers.count # 0
410
+ ```
411
+
412
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
413
+
311
414
  ### Integrações ActiveRecord e ActiveModel
312
415
 
313
416
  Para fazer uso deste recurso, você precisa de um módulo adicional.
@@ -4,6 +4,8 @@ module Micro
4
4
  module Observers
5
5
  require 'micro/observers/utils'
6
6
  require 'micro/observers/event'
7
+ require 'micro/observers/subscribers'
8
+ require 'micro/observers/broadcast'
7
9
  require 'micro/observers/set'
8
10
 
9
11
  def observers
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Micro
6
+ module Observers
7
+
8
+ module Broadcast
9
+ extend self
10
+
11
+ def call(subscribers, subject, data, event_names)
12
+ subscribers_list = subscribers.list
13
+ subscribers_to_be_deleted = ::Set.new
14
+
15
+ event_names.each(&BroadcastWith[subscribers_list, subject, data, subscribers_to_be_deleted])
16
+
17
+ subscribers_to_be_deleted.each { |subscriber| subscribers_list.delete(subscriber) }
18
+ end
19
+
20
+ private
21
+
22
+ BroadcastWith = -> (subscribers, subject, data, subscribers_to_be_deleted) do
23
+ -> (event_name) do
24
+ subscribers.each do |subscriber|
25
+ notified = NotifySubscriber.(subscriber, subject, data, event_name)
26
+ perform_once = subscriber[3]
27
+
28
+ subscribers_to_be_deleted.add(subscriber) if notified && perform_once
29
+ end
30
+ end
31
+ end
32
+
33
+ NotifySubscriber = -> (subscriber, subject, data, event_name) do
34
+ NotifyObserver.(subscriber, subject, data, event_name) ||
35
+ NotifyCallable.(subscriber, subject, data, event_name) ||
36
+ false
37
+ end
38
+
39
+ NotifyObserver = -> (subscriber, subject, data, event_name) do
40
+ strategy, observer, context = subscriber
41
+
42
+ return unless strategy == :observer && observer.respond_to?(event_name)
43
+
44
+ event = Event.new(event_name, subject, context, data)
45
+
46
+ handler = observer.is_a?(Proc) ? observer : observer.method(event.name)
47
+ handler.arity == 1 ? handler.call(event.subject) : handler.call(event.subject, event)
48
+
49
+ true
50
+ end
51
+
52
+ NotifyCallable = -> (subscriber, subject, data, event_name) do
53
+ strategy, observer, options = subscriber
54
+
55
+ return unless strategy == :callable && observer == event_name
56
+
57
+ callable, with, context = options[0], options[1], options[2]
58
+
59
+ return callable.call(with) if with && !with.is_a?(Proc)
60
+
61
+ event = Event.new(event_name, subject, context, data)
62
+
63
+ callable.call(with.is_a?(Proc) ? with.call(event) : event)
64
+
65
+ true
66
+ end
67
+
68
+ private_constant :BroadcastWith, :NotifySubscriber, :NotifyCallable, :NotifyObserver
69
+ end
70
+
71
+ private_constant :Broadcast
72
+ end
73
+ end
@@ -18,7 +18,7 @@ module Micro
18
18
  end
19
19
 
20
20
  def notify_observers_on(*callback_methods)
21
- Utils::Arrays.flatten_and_compact(callback_methods).each do |callback_method|
21
+ Utils::Arrays.fetch_from_args(callback_methods).each do |callback_method|
22
22
  self.public_send(callback_method, &notify_observers!([callback_method]))
23
23
  end
24
24
  end
@@ -4,42 +4,19 @@ module Micro
4
4
  module Observers
5
5
 
6
6
  class Set
7
- EMPTY_HASH = {}.freeze
8
-
9
- MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
10
-
11
- MapSubscribers = -> (value) do
12
- array = Utils::Arrays.flatten_and_compact(value.kind_of?(Array) ? value : [])
13
- array.map { |observer| MapSubscriber[observer, EMPTY_HASH] }
14
- end
15
-
16
- GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
17
-
18
- EqualTo = -> (observer) { -> subscriber { GetObserver[subscriber] == observer } }
19
-
20
7
  def self.for(subject)
21
8
  new(subject)
22
9
  end
23
10
 
24
11
  def initialize(subject, subscribers: nil)
25
12
  @subject = subject
26
-
27
13
  @subject_changed = false
28
-
29
- @subscribers = MapSubscribers.call(subscribers)
30
- end
31
-
32
- def count
33
- @subscribers.size
14
+ @subscribers = Subscribers.new(subscribers)
34
15
  end
35
16
 
36
- def none?
37
- @subscribers.empty?
38
- end
39
-
40
- def some?
41
- !none?
42
- end
17
+ def count; @subscribers.count; end
18
+ def none?; @subscribers.none?; end
19
+ def some?; !none?; end
43
20
 
44
21
  def subject_changed?
45
22
  @subject_changed
@@ -57,121 +34,66 @@ module Micro
57
34
  subject_changed(true)
58
35
  end
59
36
 
60
- def included?(observer)
61
- @subscribers.any?(&EqualTo[observer])
62
- end
63
-
64
- def attach(*args)
65
- options = args.last.is_a?(Hash) ? args.pop : EMPTY_HASH
66
-
67
- Utils::Arrays.flatten_and_compact(args).each do |observer|
68
- @subscribers << MapSubscriber[observer, options] unless included?(observer)
69
- end
70
-
71
- self
72
- end
73
-
74
- def detach(*args)
75
- Utils::Arrays.flatten_and_compact(args).each do |observer|
76
- @subscribers.delete_if(&EqualTo[observer])
77
- end
78
-
79
- self
37
+ def include?(observer)
38
+ @subscribers.include?(observer)
80
39
  end
40
+ alias included? include?
81
41
 
82
- def on(options = EMPTY_HASH)
83
- event, callable, with, context = options[:event], options[:call], options[:with], options[:context]
84
-
85
- return self unless event.is_a?(Symbol) && callable.respond_to?(:call)
42
+ def attach(*args); @subscribers.attach(args) and self; end
43
+ def detach(*args); @subscribers.detach(args) and self; end
86
44
 
87
- @subscribers << [:callable, event, [callable, with, context]] unless included?(callable)
45
+ def on(options = Utils::EMPTY_HASH); @subscribers.on(options) and self; end
46
+ def once(options = Utils::EMPTY_HASH); @subscribers.once(options) and self; end
88
47
 
89
- self
48
+ def off(*args)
49
+ @subscribers.off(args) and self
90
50
  end
91
51
 
92
52
  def notify(*events, data: nil)
93
53
  broadcast_if_subject_changed(Event::Names.fetch(events), data)
94
-
95
- self
96
54
  end
97
55
 
98
56
  def notify!(*events, data: nil)
99
57
  broadcast(Event::Names.fetch(events), data)
100
-
101
- self
102
58
  end
103
59
 
104
60
  CALL_EVENT = [:call].freeze
105
61
 
106
62
  def call(*events, data: nil)
107
63
  broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
108
-
109
- self
110
64
  end
111
65
 
112
66
  def call!(*events, data: nil)
113
67
  broadcast(Event::Names[events, default: CALL_EVENT], data)
114
-
115
- self
116
68
  end
117
69
 
118
70
  def inspect
119
- subs = @subscribers.empty? ? @subscribers : @subscribers.map(&GetObserver)
71
+ subs = @subscribers.to_inspect
120
72
 
121
- '<#%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
73
+ '#<%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
122
74
  end
123
75
 
124
76
  private
125
77
 
126
- def broadcast_if_subject_changed(events, data = nil)
127
- return unless subject_changed?
78
+ def broadcast_if_subject_changed(event_names, data = nil)
79
+ return self if none? || !subject_changed?
128
80
 
129
- broadcast(events, data)
81
+ broadcast(event_names, data)
130
82
 
131
83
  subject_changed(false)
132
- end
133
84
 
134
- def broadcast(event_names, data)
135
- return if @subscribers.empty?
136
-
137
- event_names.each do |event_name|
138
- @subscribers.each do |strategy, observer, context|
139
- case strategy
140
- when :observer then notify_observer(observer, event_name, context, data)
141
- when :callable then notify_callable(observer, event_name, context, data)
142
- end
143
- end
144
- end
85
+ self
145
86
  end
146
87
 
147
- def notify_observer(observer, event_name, context, data)
148
- return unless observer.respond_to?(event_name)
149
-
150
- handler = observer.is_a?(Proc) ? observer : observer.method(event_name)
151
-
152
- return handler.call(@subject) if handler.arity == 1
153
-
154
- handler.call(@subject, Event.new(event_name, @subject, context, data))
155
- end
156
-
157
- def notify_callable(expected_event_name, event_name, opt, data)
158
- return if expected_event_name != event_name
159
-
160
- callable, with, context = opt[0], opt[1], opt[2]
161
- callable_arg =
162
- if with && !with.is_a?(Proc)
163
- with
164
- else
165
- event = Event.new(event_name, @subject, context, data)
88
+ def broadcast(event_names, data)
89
+ return self if none?
166
90
 
167
- with.is_a?(Proc) ? with.call(event) : event
168
- end
91
+ Broadcast.call(@subscribers, @subject, data, event_names)
169
92
 
170
- callable.call(callable_arg)
93
+ self
171
94
  end
172
95
 
173
- private_constant :EMPTY_HASH, :INVALID_BOOLEAN_MSG, :CALL_EVENT
174
- private_constant :MapSubscriber, :MapSubscribers, :GetObserver, :EqualTo
96
+ private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
175
97
  end
176
98
 
177
99
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+
6
+ class Subscribers
7
+ EqualTo = -> (observer) { -> subscriber { GetObserver[subscriber] == observer } }
8
+ GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
9
+ MapObserver = -> (observer, options, once) { [:observer, observer, options[:context], once] }
10
+ MapObserversToInitialize = -> arg do
11
+ Utils::Arrays.flatten_and_compact(arg).map do |observer|
12
+ MapObserver[observer, Utils::EMPTY_HASH, false]
13
+ end
14
+ end
15
+
16
+ attr_reader :list
17
+
18
+ def initialize(arg)
19
+ @list = arg.is_a?(Array) ? MapObserversToInitialize[arg] : []
20
+ end
21
+
22
+ def to_inspect
23
+ none? ? @list : @list.map(&GetObserver)
24
+ end
25
+
26
+ def count
27
+ @list.size
28
+ end
29
+
30
+ def none?
31
+ @list.empty?
32
+ end
33
+
34
+ def include?(subscriber)
35
+ @list.any?(&EqualTo[subscriber])
36
+ end
37
+
38
+ def attach(args)
39
+ options = args.last.is_a?(Hash) ? args.pop : Utils::EMPTY_HASH
40
+
41
+ once = options.frozen? ? false : options.delete(:perform_once)
42
+
43
+ Utils::Arrays.fetch_from_args(args).each do |observer|
44
+ @list << MapObserver[observer, options, once] unless include?(observer)
45
+ end
46
+
47
+ true
48
+ end
49
+
50
+ def detach(args)
51
+ Utils::Arrays.fetch_from_args(args).each do |observer|
52
+ delete_observer(observer)
53
+ end
54
+
55
+ true
56
+ end
57
+
58
+ def on(options)
59
+ on!(options, once: false)
60
+ end
61
+
62
+ def once(options)
63
+ on!(options, once: true)
64
+ end
65
+
66
+ EventNameToCall = -> event_name { -> subscriber { subscriber[0] == :callable && subscriber[1] == event_name } }
67
+
68
+ def off(args)
69
+ Utils::Arrays.fetch_from_args(args).each do |value|
70
+ if value.is_a?(Symbol)
71
+ @list.delete_if(&EventNameToCall[value])
72
+ else
73
+ delete_observer(value)
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def delete_observer(observer)
81
+ @list.delete_if(&EqualTo[observer])
82
+ end
83
+
84
+ def on!(options, once:)
85
+ event, callable, with, context = options[:event], options[:call], options[:with], options[:context]
86
+
87
+ return true unless event.is_a?(Symbol) && callable.respond_to?(:call)
88
+
89
+ observer = [callable, with, context]
90
+
91
+ @list << [:callable, event, observer, once] unless include_callable?(event, observer)
92
+
93
+ true
94
+ end
95
+
96
+ CallableHaving = -> (event, observer) do
97
+ -> subs { subs[0] == :callable && subs[1] == event && subs[2] == observer }
98
+ end
99
+
100
+ def include_callable?(event, observer)
101
+ @list.any?(&CallableHaving[event, observer])
102
+ end
103
+
104
+ private_constant :EqualTo, :EventNameToCall, :CallableHaving
105
+ private_constant :GetObserver, :MapObserver, :MapObserversToInitialize
106
+ end
107
+
108
+ private_constant :Subscribers
109
+ end
110
+ end
@@ -4,9 +4,19 @@ module Micro
4
4
  module Observers
5
5
 
6
6
  module Utils
7
+ EMPTY_HASH = {}.freeze
8
+
7
9
  module Arrays
10
+ def self.fetch_from_args(args)
11
+ args.size == 1 && (first = args[0]).is_a?(::Array) ? first : args
12
+ end
13
+
8
14
  def self.flatten_and_compact(value)
9
- Array(value).flatten.tap(&:compact!)
15
+ return [] unless value
16
+
17
+ array = Array(value).flatten
18
+ array.compact!
19
+ array
10
20
  end
11
21
  end
12
22
  end
@@ -1,5 +1,5 @@
1
1
  module Micro
2
2
  module Observers
3
- VERSION = '2.1.0'
3
+ VERSION = '2.2.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.1.0
4
+ version: 2.2.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-16 00:00:00.000000000 Z
11
+ date: 2020-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,11 +58,13 @@ files:
58
58
  - bin/console
59
59
  - bin/setup
60
60
  - lib/micro/observers.rb
61
+ - lib/micro/observers/broadcast.rb
61
62
  - lib/micro/observers/event.rb
62
63
  - lib/micro/observers/event/names.rb
63
64
  - lib/micro/observers/for/active_model.rb
64
65
  - lib/micro/observers/for/active_record.rb
65
66
  - lib/micro/observers/set.rb
67
+ - lib/micro/observers/subscribers.rb
66
68
  - lib/micro/observers/utils.rb
67
69
  - lib/micro/observers/version.rb
68
70
  - lib/u-observers.rb