u-observers 2.1.0 → 2.2.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: 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