u-observers 1.0.0 → 2.0.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: 4c90f3338948cf8db02bdb1a882e3191adfd52f106fc2b4c1acf873447abd82a
4
- data.tar.gz: 2db659fbaee101e1af2838bdb89dc85838f0e9686d14fe14fd9782fc568ee1f6
3
+ metadata.gz: 5098d44090f611866c96aa4005c6475742972a9a986dbbcd5618753932b33817
4
+ data.tar.gz: 8b18f175cc5ff7fd1aae8e34302be62a1f295a151231c62e253d4b984d0225b8
5
5
  SHA512:
6
- metadata.gz: 674b8dabdc6cfca0a982c1f72582bf1fcaab34ead0f0de29d4975cc62e164c13bbded5e6118152951584fb5855b6c539510ca393b36f9cf56622bf663d7b22e2
7
- data.tar.gz: ff1e33f5029a40745172c7196d7e21552e8f4d534c71f057bf5643e3262fe8fa243fb55219eed352195a09abc51c337476f9f8bfcd5bce5f61cee416c2ec8de7
6
+ metadata.gz: e51002b6af05c7aba17eff30ba962d1d726ce9e169ae19c8a71626c035daf7c1e4e6da727c74f73c06c46ad0f5b1faaad1d1248df018ada97aa123a613e1dedf
7
+ data.tar.gz: 20972cd57db02b9a25905896af9429a597cfb22e951fe73a3af6346962cd1f3574df30c241b7aa9ef3a99c502097ad0a7d75210a33227a98c6d3435371c8bb03
data/README.md CHANGED
@@ -35,6 +35,9 @@ Because of this issue, I decided to create a gem that encapsulates the pattern w
35
35
  - [Compatibility](#compatibility)
36
36
  - [Usage](#usage)
37
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
+ - [What is a `Micro::Observers::Event`?](#what-is-a-microobserversevent)
40
+ - [Passing a callable as an observer](#passing-a-callable-as-an-observer)
38
41
  - [Calling the observers](#calling-the-observers)
39
42
  - [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
40
43
  - [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
@@ -57,11 +60,12 @@ gem 'u-observers'
57
60
 
58
61
  | u-observers | branch | ruby | activerecord |
59
62
  | ----------- | ------- | -------- | ------------- |
60
- | 1.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
63
+ | 2.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
64
+ | 1.0.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
61
65
 
62
66
  > **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).
63
67
 
64
- [⬆️ Back to Top](#table-of-contents-)
68
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
65
69
 
66
70
  ## Usage
67
71
 
@@ -102,8 +106,8 @@ end
102
106
  order = Order.new
103
107
  #<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
104
108
 
105
- order.observers.attach(OrderEvents) # attaching multiple observers. e.g. observers.attach(A, B, C)
106
- # <#Micro::Observers::Manager @subject=#<Order:0x00007fb5dd8fce70> @subject_changed=false @subscribers=[OrderEvents]
109
+ 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]
107
111
 
108
112
  order.canceled?
109
113
  # false
@@ -114,6 +118,15 @@ order.cancel!
114
118
 
115
119
  order.canceled?
116
120
  # true
121
+
122
+ 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=[]
124
+
125
+ order.canceled?
126
+ # true
127
+
128
+ order.observers.subject_changed!
129
+ order.observers.notify(:canceled) # nothing will happen, because there are no observers attached.
117
130
  ```
118
131
 
119
132
  **Highlights of the previous example:**
@@ -131,12 +144,14 @@ order.observers.notify
131
144
  # ArgumentError (no events (expected at least 1))
132
145
  ```
133
146
 
134
- [⬆️ Back to Top](#table-of-contents-)
147
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
135
148
 
136
149
  ### Passing a context for your observers
137
150
 
138
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.
139
152
 
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`.
154
+
140
155
  ```ruby
141
156
  class Order
142
157
  include Micro::Observers
@@ -149,19 +164,102 @@ class Order
149
164
  end
150
165
 
151
166
  module OrderEvents
152
- def self.canceled(order, context)
153
- puts "The order #(#{order.code}) has been canceled. (from: #{context[:from]})"
167
+ def self.canceled(order, event)
168
+ puts "The order #(#{order.object_id}) has been canceled. (from: #{event.context[:from]})" # event.ctx is an alias for event.context
154
169
  end
155
170
  end
156
171
 
157
172
  order = Order.new
158
- order.observers.attach(OrderEvents, context: { from: 'example #2' ) # attaching multiple observers. e.g. observers.attach(A, B, context: {hello: :world})
173
+ order.observers.attach(OrderEvents, context: { from: 'example #2' }) # attaching multiple observers. e.g. observers.attach(A, B, context: {hello: :world})
159
174
  order.cancel!
160
175
  # The message below will be printed by the observer (OrderEvents):
161
176
  # The order #(70196221441820) has been canceled. (from: example #2)
162
177
  ```
163
178
 
164
- [⬆️ Back to Top](#table-of-contents-)
179
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
180
+
181
+ ### Passing data when performing observers
182
+
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.
184
+
185
+ ```ruby
186
+ class Order
187
+ include Micro::Observers
188
+ end
189
+
190
+ module OrderHandler
191
+ def self.changed(order, event)
192
+ puts "The order #(#{order.object_id}) received the number #{event.data} from #{event.ctx[:from]}."
193
+ end
194
+ end
195
+
196
+ order = Order.new
197
+ order.observers.attach(OrderHandler, context: { from: 'example #3' })
198
+ order.observers.subject_changed!
199
+ order.observers.notify(:changed, data: 1)
200
+ # The message below will be printed by the observer (OrderHandler):
201
+ # The order #(70196221441820) received the number 1 from example #3.
202
+ ```
203
+
204
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
205
+
206
+ ### What is a `Micro::Observers::Event`?
207
+
208
+ The `Micro::Observers::Event` is the event payload. Follow below all of its properties:
209
+
210
+ - `#name` will be the broadcasted event.
211
+ - `#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).
214
+
215
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
216
+
217
+ ### Passing a callable as an observer
218
+
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.
223
+
224
+ ```ruby
225
+ class Person
226
+ include Micro::Observers
227
+
228
+ attr_reader :name
229
+
230
+ def initialize(name)
231
+ @name = name
232
+ end
233
+
234
+ def name=(new_name)
235
+ observers.subject_changed(new_name != @name)
236
+
237
+ return unless observers.subject_changed?
238
+
239
+ @name = new_name
240
+
241
+ observers.notify(:name_has_been_changed)
242
+ end
243
+ end
244
+
245
+ PrintPersonName = -> (data) do
246
+ puts("Person name: #{data.fetch(:person).name}, number: #{data.fetch(:number)}")
247
+ end
248
+
249
+ person = Person.new('Rodrigo')
250
+
251
+ person.observers.on(
252
+ event: :name_has_been_changed,
253
+ call: PrintPersonName,
254
+ with: -> event { {person: event.subject, number: rand} }
255
+ )
256
+
257
+ person.name = 'Serradura'
258
+ # The message below will be printed by the observer (PrintPersonName):
259
+ # Person name: Serradura, number: 0.5018509191706862
260
+ ```
261
+
262
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
165
263
 
166
264
  ### Calling the observers
167
265
 
@@ -184,13 +282,13 @@ OrderCancellation = -> (order) { puts "The order #(#{order.object_id}) has been
184
282
  order = Order.new
185
283
  order.observers.attach(OrderCancellation)
186
284
  order.cancel!
187
- # The message below will be printed by the observer (OrderEvents):
285
+ # The message below will be printed by the observer (OrderCancellation):
188
286
  # The order #(70196221441820) has been canceled.
189
287
  ```
190
288
 
191
- PS: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.a
289
+ > **Note**: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.a
192
290
 
193
- [⬆️ Back to Top](#table-of-contents-)
291
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
194
292
 
195
293
  ### Notifying observers without marking them as changed
196
294
 
@@ -198,7 +296,7 @@ This feature needs to be used with caution!
198
296
 
199
297
  If you use the methods `#notify!` or `#call!` you won't need to mark observers with `#subject_changed`.
200
298
 
201
- [⬆️ Back to Top](#table-of-contents-)
299
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
202
300
 
203
301
  ### ActiveRecord and ActiveModel integrations
204
302
 
@@ -230,22 +328,22 @@ module TitlePrinter
230
328
  end
231
329
 
232
330
  module TitlePrinterWithContext
233
- def self.after_commit(post, context)
234
- puts "Title: #{post.title} (from: #{context[:from]})"
331
+ def self.after_commit(post, event)
332
+ puts "Title: #{post.title} (from: #{event.context[:from]})"
235
333
  end
236
334
  end
237
335
 
238
336
  Post.transaction do
239
337
  post = Post.new(title: 'Hello world')
240
- post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example 4' })
338
+ post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #6' })
241
339
  post.save
242
340
  end
243
- # The message below will be printed by the observer (OrderEvents):
341
+ # The message below will be printed by the observers (TitlePrinter, TitlePrinterWithContext):
244
342
  # Title: Hello world
245
- # Title: Hello world (from: example 4)
343
+ # Title: Hello world (from: example #6)
246
344
  ```
247
345
 
248
- [⬆️ Back to Top](#table-of-contents-)
346
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
249
347
 
250
348
  #### notify_observers()
251
349
 
@@ -265,24 +363,24 @@ module TitlePrinter
265
363
  end
266
364
 
267
365
  module TitlePrinterWithContext
268
- def self.transaction_completed(post, context)
269
- puts("Title: #{post.title} (from: #{context[:from]})")
366
+ def self.transaction_completed(post, event)
367
+ puts("Title: #{post.title} (from: #{event.ctx[:from]})")
270
368
  end
271
369
  end
272
370
 
273
371
  Post.transaction do
274
372
  post = Post.new(title: 'Olá mundo')
275
- post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example 5' })
373
+ post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example #7' })
276
374
  post.save
277
375
  end
278
- # The message below will be printed by the observer (OrderEvents):
376
+ # The message below will be printed by the observers (TitlePrinter, TitlePrinterWithContext):
279
377
  # Title: Olá mundo
280
- # Title: Olá mundo (from: example 5)
378
+ # Title: Olá mundo (from: example #5)
281
379
  ```
282
380
 
283
- PS: You can use `include ::Micro::Observers::For::ActiveModel` if your class only makes use of the `ActiveModel` and all the previous examples will work.
381
+ > **Note**: You can use `include ::Micro::Observers::For::ActiveModel` if your class only makes use of the `ActiveModel` and all the previous examples will work.
284
382
 
285
- [⬆️ Back to Top](#table-of-contents-)
383
+ [⬆️ &nbsp; Back to Top](#table-of-contents-)
286
384
 
287
385
  ## Development
288
386
 
@@ -3,11 +3,11 @@ require 'micro/observers/version'
3
3
  module Micro
4
4
  module Observers
5
5
  require 'micro/observers/utils'
6
- require 'micro/observers/events'
7
- require 'micro/observers/manager'
6
+ require 'micro/observers/event'
7
+ require 'micro/observers/set'
8
8
 
9
9
  def observers
10
- @__observers ||= Observers::Manager.for(self)
10
+ @__observers ||= Observers::Set.for(self)
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+
6
+ class Event
7
+ require 'micro/observers/event/names'
8
+
9
+ attr_reader :name, :subject, :context, :data
10
+
11
+ def initialize(name, subject, context, data)
12
+ @name, @subject = name, subject
13
+ @context, @data = context, data
14
+ end
15
+
16
+ alias ctx context
17
+ alias subj subject
18
+ end
19
+
20
+ end
21
+ end
@@ -3,7 +3,7 @@
3
3
  module Micro
4
4
  module Observers
5
5
 
6
- module Events
6
+ class Event::Names
7
7
  def self.[](value, default: Utils::EMPTY_ARRAY)
8
8
  values = Utils.compact_array(value)
9
9
 
@@ -14,7 +14,7 @@ module Micro
14
14
  end
15
15
 
16
16
  def notify_observers(*events)
17
- notify_observers!(Events.fetch(events))
17
+ notify_observers!(Event::Names.fetch(events))
18
18
  end
19
19
 
20
20
  def notify_observers_on(*callback_methods)
@@ -3,7 +3,7 @@
3
3
  module Micro
4
4
  module Observers
5
5
 
6
- class Manager
6
+ class Set
7
7
  MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
8
8
 
9
9
  MapSubscribers = -> (value) do
@@ -46,11 +46,7 @@ module Micro
46
46
  INVALID_BOOLEAN_MSG = 'expected a boolean (true, false)'.freeze
47
47
 
48
48
  def subject_changed(state)
49
- if state == true || state == false
50
- @subject_changed = state
51
-
52
- return self
53
- end
49
+ return @subject_changed = state if state == true || state == false
54
50
 
55
51
  raise ArgumentError, INVALID_BOOLEAN_MSG
56
52
  end
@@ -91,28 +87,28 @@ module Micro
91
87
  self
92
88
  end
93
89
 
94
- def notify(*events)
95
- broadcast_if_subject_changed(Events.fetch(events))
90
+ def notify(*events, data: nil)
91
+ broadcast_if_subject_changed(Event::Names.fetch(events), data)
96
92
 
97
93
  self
98
94
  end
99
95
 
100
- def notify!(*events)
101
- broadcast(Events.fetch(events))
96
+ def notify!(*events, data: nil)
97
+ broadcast(Event::Names.fetch(events), data)
102
98
 
103
99
  self
104
100
  end
105
101
 
106
102
  CALL_EVENT = [:call].freeze
107
103
 
108
- def call(*events)
109
- broadcast_if_subject_changed(Events[events, default: CALL_EVENT])
104
+ def call(*events, data: nil)
105
+ broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
110
106
 
111
107
  self
112
108
  end
113
109
 
114
- def call!(*events)
115
- broadcast(Events[events, default: CALL_EVENT])
110
+ def call!(*events, data: nil)
111
+ broadcast(Event::Names[events, default: CALL_EVENT], data)
116
112
 
117
113
  self
118
114
  end
@@ -125,43 +121,51 @@ module Micro
125
121
 
126
122
  private
127
123
 
128
- def broadcast_if_subject_changed(events)
124
+ def broadcast_if_subject_changed(events, data = nil)
129
125
  return unless subject_changed?
130
126
 
131
- broadcast(events)
127
+ broadcast(events, data)
132
128
 
133
129
  subject_changed(false)
134
130
  end
135
131
 
136
- def broadcast(events)
132
+ def broadcast(event_names, data)
137
133
  return if @subscribers.empty?
138
134
 
139
- events.each do |event|
135
+ event_names.each do |event_name|
140
136
  @subscribers.each do |strategy, observer, context|
141
137
  case strategy
142
- when :observer then notify_observer(observer, event, context)
143
- when :callable then notify_callable(observer, event, context)
138
+ when :observer then notify_observer(observer, event_name, context, data)
139
+ when :callable then notify_callable(observer, event_name, context, data)
144
140
  end
145
141
  end
146
142
  end
147
143
  end
148
144
 
149
- def notify_observer(observer, method_name, context)
150
- return unless observer.respond_to?(method_name)
145
+ def notify_observer(observer, event_name, context, data)
146
+ return unless observer.respond_to?(event_name)
151
147
 
152
- handler = observer.is_a?(Proc) ? observer : observer.method(method_name)
148
+ handler = observer.is_a?(Proc) ? observer : observer.method(event_name)
153
149
 
154
- handler.arity == 1 ? handler.call(@subject) : handler.call(@subject, context)
150
+ return handler.call(@subject) if handler.arity == 1
151
+
152
+ handler.call(@subject, Event.new(event_name, @subject, context, data))
155
153
  end
156
154
 
157
- def notify_callable(expected_event, event, context)
158
- return if expected_event != event
155
+ def notify_callable(expected_event_name, event_name, context, data)
156
+ return if expected_event_name != event_name
159
157
 
160
158
  callable, with = context[0], context[1]
159
+ callable_arg =
160
+ if with && !with.is_a?(Proc)
161
+ with
162
+ else
163
+ event = Event.new(event_name, @subject, nil, data)
161
164
 
162
- arg = with.is_a?(Proc) ? with.call(@subject) : (with || @subject)
165
+ with.is_a?(Proc) ? with.call(event) : event
166
+ end
163
167
 
164
- callable.call(arg)
168
+ callable.call(callable_arg)
165
169
  end
166
170
 
167
171
  private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
@@ -1,5 +1,5 @@
1
1
  module Micro
2
2
  module Observers
3
- VERSION = '1.0.0'
3
+ VERSION = '2.0.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: 1.0.0
4
+ version: 2.0.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-05 00:00:00.000000000 Z
11
+ date: 2020-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -57,10 +57,11 @@ files:
57
57
  - bin/console
58
58
  - bin/setup
59
59
  - lib/micro/observers.rb
60
- - lib/micro/observers/events.rb
60
+ - lib/micro/observers/event.rb
61
+ - lib/micro/observers/event/names.rb
61
62
  - lib/micro/observers/for/active_model.rb
62
63
  - lib/micro/observers/for/active_record.rb
63
- - lib/micro/observers/manager.rb
64
+ - lib/micro/observers/set.rb
64
65
  - lib/micro/observers/utils.rb
65
66
  - lib/micro/observers/version.rb
66
67
  - lib/u-observers.rb