u-observers 1.0.0 → 2.0.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: 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