u-observers 0.5.0 → 1.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: 2268ebc8924bf446ca73f3b1fbdb0cb4a8fba9f60eb01173e68fa7486747b1ed
4
- data.tar.gz: 329fad95d53450a550c9fc964fec4bb3732f5a63d684835f0ef6d766df839d15
3
+ metadata.gz: 4c90f3338948cf8db02bdb1a882e3191adfd52f106fc2b4c1acf873447abd82a
4
+ data.tar.gz: 2db659fbaee101e1af2838bdb89dc85838f0e9686d14fe14fd9782fc568ee1f6
5
5
  SHA512:
6
- metadata.gz: d3a2f1895221f171ca6499bca6284a9d349e77b9d6661b41a0b15c662d9f400af86d7d0b77e4331ca9a985b6db2d6628415be5422944cb09aba32d676aa537eb
7
- data.tar.gz: 82daa067cd2cf85c3b00c0aca546131e38c1ee365336d5f01cb868b81d7b91be806ecb606c61ad5e56d2364f9341f8636d093f02d6b6f424ddccc0702b11f7e7
6
+ metadata.gz: 674b8dabdc6cfca0a982c1f72582bf1fcaab34ead0f0de29d4975cc62e164c13bbded5e6118152951584fb5855b6c539510ca393b36f9cf56622bf663d7b22e2
7
+ data.tar.gz: ff1e33f5029a40745172c7196d7e21552e8f4d534c71f057bf5643e3262fe8fa243fb55219eed352195a09abc51c337476f9f8bfcd5bce5f61cee416c2ec8de7
@@ -0,0 +1 @@
1
+ ruby 2.6.5
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+
3
+ ruby_v=$(ruby -v)
4
+
5
+ bundle update
6
+ bundle exec rake test
7
+
8
+ ACTIVERECORD_VERSION='3.2' bundle update
9
+ ACTIVERECORD_VERSION='3.2' bundle exec rake test
10
+
11
+ ACTIVERECORD_VERSION='4.0' bundle update
12
+ ACTIVERECORD_VERSION='4.0' bundle exec rake test
13
+
14
+ ACTIVERECORD_VERSION='4.1' bundle update
15
+ ACTIVERECORD_VERSION='4.1' bundle exec rake test
16
+
17
+ ACTIVERECORD_VERSION='4.2' bundle update
18
+ ACTIVERECORD_VERSION='4.2' bundle exec rake test
19
+
20
+ ACTIVERECORD_VERSION='5.0' bundle update
21
+ ACTIVERECORD_VERSION='5.0' bundle exec rake test
22
+
23
+ ACTIVERECORD_VERSION='5.1' bundle update
24
+ ACTIVERECORD_VERSION='5.1' bundle exec rake test
25
+
26
+ if [[ ! $ruby_v =~ '2.2.0' ]]; then
27
+ ACTIVERECORD_VERSION='5.2' bundle update
28
+ ACTIVERECORD_VERSION='5.2' bundle exec rake test
29
+ fi
30
+
31
+ if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
32
+ ACTIVERECORD_VERSION='6.0' bundle update
33
+ ACTIVERECORD_VERSION='6.0' bundle exec rake test
34
+ fi
@@ -1,6 +1,30 @@
1
1
  ---
2
2
  language: ruby
3
- cache: bundler
3
+
4
+ sudo: false
5
+
4
6
  rvm:
5
- - 2.6.5
6
- before_install: gem install bundler -v 2.1.4
7
+ - 2.2.0
8
+ - 2.3.0
9
+ - 2.4.0
10
+ - 2.5.0
11
+ - 2.6.0
12
+ - 2.7.0
13
+
14
+ cache: bundler
15
+
16
+ before_install:
17
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
18
+ - gem install bundler -v '< 2'
19
+
20
+ install: bundle install --jobs=3 --retry=3
21
+
22
+ before_script:
23
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
24
+ - chmod +x ./cc-test-reporter
25
+ - "./cc-test-reporter before-build"
26
+
27
+ script: "./.travis.sh"
28
+
29
+ after_success:
30
+ - "./cc-test-reporter after-build -t simplecov"
data/Gemfile CHANGED
@@ -3,12 +3,38 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in u-observers.gemspec
4
4
  gemspec
5
5
 
6
- gem 'rake', '~> 12.0'
7
- gem 'minitest', '~> 5.0'
6
+ activerecord_version = ENV.fetch('ACTIVERECORD_VERSION', '6.1')
7
+
8
+ activerecord = case activerecord_version
9
+ when '3.2' then '3.2.22'
10
+ when '4.0' then '4.0.13'
11
+ when '4.1' then '4.1.16'
12
+ when '4.2' then '4.2.11'
13
+ when '5.0' then '5.0.7'
14
+ when '5.1' then '5.1.7'
15
+ when '5.2' then '5.2.3'
16
+ when '6.0' then '6.0.3'
17
+ end
18
+
19
+ simplecov_version =
20
+ case RUBY_VERSION
21
+ when /\A2.[23]/ then '~> 0.17.1'
22
+ when /\A2.4/ then '~> 0.18.5'
23
+ else '~> 0.19'
24
+ end
8
25
 
9
26
  group :test do
10
- gem 'activerecord', require: 'active_record'
11
- gem 'sqlite3'
27
+ gem 'minitest', activerecord_version < '4.1' ? '~> 4.2' : '~> 5.0'
28
+ gem 'simplecov', simplecov_version, require: false
29
+
30
+ if activerecord
31
+ sqlite3 =
32
+ case activerecord
33
+ when /\A6\.0/, nil then '~> 1.4.0'
34
+ else '~> 1.3.0'
35
+ end
12
36
 
13
- gem 'simplecov', '~> 0.19', require: false
37
+ gem 'sqlite3', sqlite3
38
+ gem 'activerecord', activerecord, require: 'active_record'
39
+ end
14
40
  end
data/README.md CHANGED
@@ -1,28 +1,288 @@
1
- # Micro::Observers
1
+ <p align="center">
2
+ <h1 align="center">👀 μ-observers</h1>
3
+ <p align="center"><i>Simple and powerful implementation of the observer pattern.</i></p>
4
+ <br>
5
+ </p>
2
6
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/micro/observers`. To experiment with that code, run `bin/console` for an interactive prompt.
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/ruby->%3D%202.2.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
4
9
 
5
- TODO: Delete this and the text above, and describe your gem
10
+ <a href="https://rubygems.org/gems/u-observers">
11
+ <img alt="Gem" src="https://img.shields.io/gem/v/u-observers.svg?style=flat-square">
12
+ </a>
6
13
 
7
- ## Installation
14
+ <a href="https://travis-ci.com/serradura/u-observers">
15
+ <img alt="Build Status" src="https://travis-ci.com/serradura/u-observers.svg?branch=main">
16
+ </a>
8
17
 
9
- Add this line to your application's Gemfile:
18
+ <a href="https://codeclimate.com/github/serradura/u-observers/maintainability">
19
+ <img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/e72ffa84bc95c59823f2/maintainability">
20
+ </a>
21
+
22
+ <a href="https://codeclimate.com/github/serradura/u-observers/test_coverage">
23
+ <img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/e72ffa84bc95c59823f2/test_coverage">
24
+ </a>
25
+ </p>
26
+
27
+ This gem implements the observer pattern [[1]](https://en.wikipedia.org/wiki/Observer_pattern)[[2]](https://refactoring.guru/design-patterns/observer) (also known as publish/subscribe). It provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.
28
+
29
+ Ruby's standard library [has an abstraction](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html) that enables you to use this pattern. But its design can conflict with other mainstream libraries, like the [`ActiveModel`/`ActiveRecord`](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed), which also has the [`changed`](https://ruby-doc.org/stdlib-2.7.1/libdoc/observer/rdoc/Observable.html#method-i-changed) method. In this case, the behavior of the Stdlib will be been compromised.
30
+
31
+ Because of this issue, I decided to create a gem that encapsulates the pattern without changing the object's implementation so much. The `Micro::Observers` includes just one instance method in the target class (its instance will be the observed subject).
32
+
33
+ # Table of contents <!-- omit in toc -->
34
+ - [Installation](#installation)
35
+ - [Compatibility](#compatibility)
36
+ - [Usage](#usage)
37
+ - [Passing a context for your observers](#passing-a-context-for-your-observers)
38
+ - [Calling the observers](#calling-the-observers)
39
+ - [Notifying observers without marking them as changed](#notifying-observers-without-marking-them-as-changed)
40
+ - [ActiveRecord and ActiveModel integrations](#activerecord-and-activemodel-integrations)
41
+ - [notify_observers_on()](#notify_observers_on)
42
+ - [notify_observers()](#notify_observers)
43
+ - [Development](#development)
44
+ - [Contributing](#contributing)
45
+ - [License](#license)
46
+ - [Code of Conduct](#code-of-conduct)
47
+
48
+ # Installation
49
+
50
+ Add this line to your application's Gemfile and `bundle install`:
10
51
 
11
52
  ```ruby
12
- gem 'micro-observers'
53
+ gem 'u-observers'
13
54
  ```
14
55
 
15
- And then execute:
56
+ # Compatibility
16
57
 
17
- $ bundle install
58
+ | u-observers | branch | ruby | activerecord |
59
+ | ----------- | ------- | -------- | ------------- |
60
+ | 1.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
18
61
 
19
- Or install it yourself as:
62
+ > **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).
20
63
 
21
- $ gem install u-observers
64
+ [⬆️ Back to Top](#table-of-contents-)
22
65
 
23
66
  ## Usage
24
67
 
25
- TODO: Write usage instructions here
68
+ Any class with `Micro::Observers` module included can notify events to attached observers.
69
+
70
+ ```ruby
71
+ require 'securerandom'
72
+
73
+ class Order
74
+ include Micro::Observers
75
+
76
+ attr_reader :code
77
+
78
+ def initialize
79
+ @code, @status = SecureRandom.alphanumeric, :draft
80
+ end
81
+
82
+ def canceled?
83
+ @status == :canceled
84
+ end
85
+
86
+ def cancel!
87
+ return self if canceled?
88
+
89
+ @status = :canceled
90
+
91
+ observers.subject_changed!
92
+ observers.notify(:canceled) and return self
93
+ end
94
+ end
95
+
96
+ module OrderEvents
97
+ def self.canceled(order)
98
+ puts "The order #(#{order.code}) has been canceled."
99
+ end
100
+ end
101
+
102
+ order = Order.new
103
+ #<Order:0x00007fb5dd8fce70 @code="X0o9yf1GsdQFvLR4", @status=:draft>
104
+
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]
107
+
108
+ order.canceled?
109
+ # false
110
+
111
+ order.cancel!
112
+ # The message below will be printed by the observer (OrderEvents):
113
+ # The order #(X0o9yf1GsdQFvLR4) has been canceled
114
+
115
+ order.canceled?
116
+ # true
117
+ ```
118
+
119
+ **Highlights of the previous example:**
120
+
121
+ To avoid an undesired behavior, do you need to mark the subject as changed before notify your observers about some event.
122
+
123
+ You can do this when using the `#subject_changed!` method. It will automatically mark the subject as changed.
124
+
125
+ But if you need to apply some conditional to mark a change, you can use the `#subject_changed` method. e.g. `observers.subject_changed(name != new_name)`
126
+
127
+ The `#notify` method always requires an event to make a broadcast. So, if you try to use it without one or more events (symbol values) you will get an exception.
128
+
129
+ ```ruby
130
+ order.observers.notify
131
+ # ArgumentError (no events (expected at least 1))
132
+ ```
133
+
134
+ [⬆️ Back to Top](#table-of-contents-)
135
+
136
+ ### Passing a context for your observers
137
+
138
+ 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
+
140
+ ```ruby
141
+ class Order
142
+ include Micro::Observers
143
+
144
+ def cancel!
145
+ observers.subject_changed!
146
+ observers.notify(:canceled)
147
+ self
148
+ end
149
+ end
150
+
151
+ module OrderEvents
152
+ def self.canceled(order, context)
153
+ puts "The order #(#{order.code}) has been canceled. (from: #{context[:from]})"
154
+ end
155
+ end
156
+
157
+ order = Order.new
158
+ order.observers.attach(OrderEvents, context: { from: 'example #2' ) # attaching multiple observers. e.g. observers.attach(A, B, context: {hello: :world})
159
+ order.cancel!
160
+ # The message below will be printed by the observer (OrderEvents):
161
+ # The order #(70196221441820) has been canceled. (from: example #2)
162
+ ```
163
+
164
+ [⬆️ Back to Top](#table-of-contents-)
165
+
166
+ ### Calling the observers
167
+
168
+ You can use a callable (a class, module, or object that responds to the call method) to be your observers.
169
+ To do this, you only need make use of the method `#call` instead of `#notify`.
170
+
171
+ ```ruby
172
+ class Order
173
+ include Micro::Observers
174
+
175
+ def cancel!
176
+ observers.subject_changed!
177
+ observers.call # in practice, this is a shortcut to observers.notify(:call)
178
+ self
179
+ end
180
+ end
181
+
182
+ OrderCancellation = -> (order) { puts "The order #(#{order.object_id}) has been canceled." }
183
+
184
+ order = Order.new
185
+ order.observers.attach(OrderCancellation)
186
+ order.cancel!
187
+ # The message below will be printed by the observer (OrderEvents):
188
+ # The order #(70196221441820) has been canceled.
189
+ ```
190
+
191
+ PS: The `observers.call` can receive one or more events, but in this case, the default event (`call`) won't be transmitted.a
192
+
193
+ [⬆️ Back to Top](#table-of-contents-)
194
+
195
+ ### Notifying observers without marking them as changed
196
+
197
+ This feature needs to be used with caution!
198
+
199
+ If you use the methods `#notify!` or `#call!` you won't need to mark observers with `#subject_changed`.
200
+
201
+ [⬆️ Back to Top](#table-of-contents-)
202
+
203
+ ### ActiveRecord and ActiveModel integrations
204
+
205
+ To make use of this feature you need to require an additional module (`require 'u-observers/for/active_record'`).
206
+
207
+ Gemfile example:
208
+ ```ruby
209
+ gem 'u-observers', require: 'u-observers/for/active_record'
210
+ ```
211
+
212
+ 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:
213
+
214
+
215
+ #### notify_observers_on()
216
+
217
+ The `notify_observers_on` allows you to pass one or more `ActiveModel`/`ActiveRecord` callbacks, that will be used to notify your object observers.
218
+
219
+ ```ruby
220
+ class Post < ActiveRecord::Base
221
+ include ::Micro::Observers::For::ActiveRecord
222
+
223
+ notify_observers_on(:after_commit) # passing multiple callbacks. e.g. notify_observers_on(:before_save, :after_commit)
224
+ end
225
+
226
+ module TitlePrinter
227
+ def self.after_commit(post)
228
+ puts "Title: #{post.title}"
229
+ end
230
+ end
231
+
232
+ module TitlePrinterWithContext
233
+ def self.after_commit(post, context)
234
+ puts "Title: #{post.title} (from: #{context[:from]})"
235
+ end
236
+ end
237
+
238
+ Post.transaction do
239
+ post = Post.new(title: 'Hello world')
240
+ post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example 4' })
241
+ post.save
242
+ end
243
+ # The message below will be printed by the observer (OrderEvents):
244
+ # Title: Hello world
245
+ # Title: Hello world (from: example 4)
246
+ ```
247
+
248
+ [⬆️ Back to Top](#table-of-contents-)
249
+
250
+ #### notify_observers()
251
+
252
+ The `notify_observers` allows you to pass one or more *events*, that will be used to notify after the execution of some `ActiveModel`/`ActiveRecord` callback.
253
+
254
+ ```ruby
255
+ class Post < ActiveRecord::Base
256
+ include ::Micro::Observers::For::ActiveRecord
257
+
258
+ after_commit(&notify_observers(:transaction_completed))
259
+ end
260
+
261
+ module TitlePrinter
262
+ def self.transaction_completed(post)
263
+ puts("Title: #{post.title}")
264
+ end
265
+ end
266
+
267
+ module TitlePrinterWithContext
268
+ def self.transaction_completed(post, context)
269
+ puts("Title: #{post.title} (from: #{context[:from]})")
270
+ end
271
+ end
272
+
273
+ Post.transaction do
274
+ post = Post.new(title: 'Olá mundo')
275
+ post.observers.attach(TitlePrinter, TitlePrinterWithContext, context: { from: 'example 5' })
276
+ post.save
277
+ end
278
+ # The message below will be printed by the observer (OrderEvents):
279
+ # Title: Olá mundo
280
+ # Title: Olá mundo (from: example 5)
281
+ ```
282
+
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.
284
+
285
+ [⬆️ Back to Top](#table-of-contents-)
26
286
 
27
287
  ## Development
28
288
 
@@ -3,31 +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_or_actions'
6
+ require 'micro/observers/events'
7
7
  require 'micro/observers/manager'
8
8
 
9
- module ClassMethods
10
- def notify_observers!(with:)
11
- proc { |object| with.each { |evt_or_act| object.observers.notify(evt_or_act) } }
12
- end
13
-
14
- def notify_observers(*events)
15
- notify_observers!(with: EventsOrActions[events])
16
- end
17
-
18
- def call_observers(options = Utils::EMPTY_HASH)
19
- notify_observers!(with: EventsOrActions.fetch_actions(options))
20
- end
21
- end
22
-
23
- def self.included(base)
24
- base.extend(ClassMethods)
25
- base.send(:private_class_method, :notify_observers!)
26
- end
27
-
28
9
  def observers
29
- @observers ||= Observers::Manager.for(self)
10
+ @__observers ||= Observers::Manager.for(self)
30
11
  end
31
-
32
12
  end
33
13
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+
6
+ module Events
7
+ def self.[](value, default: Utils::EMPTY_ARRAY)
8
+ values = Utils.compact_array(value)
9
+
10
+ values.empty? ? default : values
11
+ end
12
+
13
+ NO_EVENTS_MSG = 'no events (expected at least 1)'.freeze
14
+
15
+ def self.fetch(value)
16
+ values = self[value]
17
+
18
+ return values unless values.empty?
19
+
20
+ raise ArgumentError, NO_EVENTS_MSG
21
+ end
22
+
23
+ private_constant :NO_EVENTS_MSG
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+ module For
6
+
7
+ module ActiveModel
8
+ module ClassMethods
9
+ def notify_observers!(events)
10
+ proc do |object|
11
+ object.observers.subject_changed!
12
+ object.observers.send(:broadcast_if_subject_changed, events)
13
+ end
14
+ end
15
+
16
+ def notify_observers(*events)
17
+ notify_observers!(Events.fetch(events))
18
+ end
19
+
20
+ def notify_observers_on(*callback_methods)
21
+ Utils.compact_array(callback_methods).each do |callback_method|
22
+ self.public_send(callback_method, &notify_observers!([callback_method]))
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.included(base)
28
+ base.extend(ClassMethods)
29
+ base.send(:private_class_method, :notify_observers!)
30
+ base.send(:include, ::Micro::Observers)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+ module For
6
+ require 'micro/observers/for/active_model'
7
+
8
+ module ActiveRecord
9
+ def self.included(base)
10
+ base.send(:include, ::Micro::Observers::For::ActiveModel)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -4,27 +4,78 @@ module Micro
4
4
  module Observers
5
5
 
6
6
  class Manager
7
- EqualTo = -> (observer) do
8
- -> item { item[0] == :observer && item[1] == observer }
7
+ MapSubscriber = -> (observer, options) { [:observer, observer, options[:context]] }
8
+
9
+ MapSubscribers = -> (value) do
10
+ array = Utils.compact_array(value.kind_of?(Array) ? value : [])
11
+ array.map { |observer| MapSubscriber[observer, Utils::EMPTY_HASH] }
9
12
  end
10
13
 
14
+ GetObserver = -> subscriber { subscriber[0] == :observer ? subscriber[1] : subscriber[2][0] }
15
+
16
+ EqualTo = -> (observer) { -> subscriber { GetObserver[subscriber] == observer } }
17
+
11
18
  def self.for(subject)
12
19
  new(subject)
13
20
  end
14
21
 
15
- def initialize(subject, list = nil)
22
+ def initialize(subject, subscribers: nil)
16
23
  @subject = subject
17
24
 
18
- @list = Utils.compact_array(list.kind_of?(Array) ? list : [])
25
+ @subject_changed = false
26
+
27
+ @subscribers = MapSubscribers.call(subscribers)
28
+ end
29
+
30
+ def count
31
+ @subscribers.size
32
+ end
33
+
34
+ def none?
35
+ @subscribers.empty?
36
+ end
37
+
38
+ def some?
39
+ !none?
40
+ end
41
+
42
+ def subject_changed?
43
+ @subject_changed
44
+ end
45
+
46
+ INVALID_BOOLEAN_MSG = 'expected a boolean (true, false)'.freeze
47
+
48
+ def subject_changed(state)
49
+ if state == true || state == false
50
+ @subject_changed = state
51
+
52
+ return self
53
+ end
54
+
55
+ raise ArgumentError, INVALID_BOOLEAN_MSG
56
+ end
57
+
58
+ def subject_changed!
59
+ subject_changed(true)
19
60
  end
20
61
 
21
62
  def included?(observer)
22
- @list.any?(&EqualTo[observer])
63
+ @subscribers.any?(&EqualTo[observer])
23
64
  end
24
65
 
25
- def attach(observer, options = Utils::EMPTY_HASH)
26
- if options[:allow_duplication] || !included?(observer)
27
- @list << [:observer, observer, options[:data]]
66
+ def attach(*args)
67
+ options = args.last.is_a?(Hash) ? args.pop : Utils::EMPTY_HASH
68
+
69
+ Utils.compact_array(args).each do |observer|
70
+ @subscribers << MapSubscriber[observer, options] unless included?(observer)
71
+ end
72
+
73
+ self
74
+ end
75
+
76
+ def detach(*args)
77
+ Utils.compact_array(args).each do |observer|
78
+ @subscribers.delete_if(&EqualTo[observer])
28
79
  end
29
80
 
30
81
  self
@@ -35,50 +86,86 @@ module Micro
35
86
 
36
87
  return self unless event.is_a?(Symbol) && callable.respond_to?(:call)
37
88
 
38
- arg = with.is_a?(Proc) ? with.call(@subject) : (arg || subject)
89
+ @subscribers << [:callable, event, [callable, with]] unless included?(callable)
39
90
 
40
- @list << [:callable, event, [callable, arg]]
91
+ self
41
92
  end
42
93
 
43
- def detach(observer)
44
- @list.delete_if(&EqualTo[observer])
94
+ def notify(*events)
95
+ broadcast_if_subject_changed(Events.fetch(events))
45
96
 
46
97
  self
47
98
  end
48
99
 
49
- def notify(*events)
50
- EventsOrActions[events].each { |act| notify!(act) }
100
+ def notify!(*events)
101
+ broadcast(Events.fetch(events))
102
+
103
+ self
104
+ end
105
+
106
+ CALL_EVENT = [:call].freeze
107
+
108
+ def call(*events)
109
+ broadcast_if_subject_changed(Events[events, default: CALL_EVENT])
51
110
 
52
111
  self
53
112
  end
54
113
 
55
- def call(options = Utils::EMPTY_HASH)
56
- EventsOrActions.fetch_actions(options).each { |act| notify!(act) }
114
+ def call!(*events)
115
+ broadcast(Events[events, default: CALL_EVENT])
57
116
 
58
117
  self
59
118
  end
60
119
 
120
+ def inspect
121
+ subs = @subscribers.empty? ? @subscribers : @subscribers.map(&GetObserver)
122
+
123
+ '<#%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
124
+ end
125
+
61
126
  private
62
127
 
63
- def notify!(evt_or_act)
64
- @list.each do |strategy, observer, data|
65
- call!(observer, strategy, data, with: evt_or_act)
128
+ def broadcast_if_subject_changed(events)
129
+ return unless subject_changed?
130
+
131
+ broadcast(events)
132
+
133
+ subject_changed(false)
134
+ end
135
+
136
+ def broadcast(events)
137
+ return if @subscribers.empty?
138
+
139
+ events.each do |event|
140
+ @subscribers.each do |strategy, observer, context|
141
+ case strategy
142
+ when :observer then notify_observer(observer, event, context)
143
+ when :callable then notify_callable(observer, event, context)
144
+ end
145
+ end
66
146
  end
67
147
  end
68
148
 
69
- def call!(observer, strategy, data, with:)
70
- return data[0].call(data[1]) if strategy == :callable && observer == with
149
+ def notify_observer(observer, method_name, context)
150
+ return unless observer.respond_to?(method_name)
71
151
 
72
- if strategy == :observer && observer.respond_to?(with)
73
- handler = observer.method(with)
152
+ handler = observer.is_a?(Proc) ? observer : observer.method(method_name)
74
153
 
75
- return handler.call(@subject) if handler.arity == 1
154
+ handler.arity == 1 ? handler.call(@subject) : handler.call(@subject, context)
155
+ end
76
156
 
77
- handler.call(@subject, data)
78
- end
157
+ def notify_callable(expected_event, event, context)
158
+ return if expected_event != event
159
+
160
+ callable, with = context[0], context[1]
161
+
162
+ arg = with.is_a?(Proc) ? with.call(@subject) : (with || @subject)
163
+
164
+ callable.call(arg)
79
165
  end
80
166
 
81
- private_constant :EqualTo
167
+ private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
168
+ private_constant :MapSubscriber, :MapSubscribers, :GetObserver, :EqualTo
82
169
  end
83
170
 
84
171
  end
@@ -5,6 +5,7 @@ module Micro
5
5
 
6
6
  module Utils
7
7
  EMPTY_HASH = {}.freeze
8
+ EMPTY_ARRAY = [].freeze
8
9
 
9
10
  def self.compact_array(value)
10
11
  Array(value).flatten.tap(&:compact!)
@@ -1,5 +1,5 @@
1
1
  module Micro
2
2
  module Observers
3
- VERSION = '0.5.0'
3
+ VERSION = '1.0.0'
4
4
  end
5
5
  end
@@ -0,0 +1,2 @@
1
+ require 'micro/observers'
2
+ require 'micro/observers/for/active_model'
@@ -0,0 +1,2 @@
1
+ require 'micro/observers'
2
+ require 'micro/observers/for/active_record'
data/test.sh ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ bundle
4
+
5
+ rm Gemfile.lock
6
+
7
+ source $(dirname $0)/.travis.sh
8
+
9
+ rm Gemfile.lock
10
+
11
+ bundle
@@ -10,7 +10,6 @@ Gem::Specification.new do |spec|
10
10
  spec.description = %q{Simple and powerful implementation of the observer pattern.}
11
11
  spec.homepage = 'https://github.com/serradura/u-observers'
12
12
  spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 2.2.0')
14
13
 
15
14
  spec.metadata['homepage_uri'] = spec.homepage
16
15
  spec.metadata['source_code_uri'] = 'https://github.com/serradura/u-observers'
@@ -24,4 +23,9 @@ Gem::Specification.new do |spec|
24
23
  spec.bindir = 'exe'
25
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
25
  spec.require_paths = ['lib']
26
+
27
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.2.0')
28
+
29
+ spec.add_development_dependency 'bundler'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
27
31
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-observers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.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-09-26 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2020-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
13
41
  description: Simple and powerful implementation of the observer pattern.
14
42
  email:
15
43
  - rodrigo.serradura@gmail.com
@@ -18,6 +46,8 @@ extensions: []
18
46
  extra_rdoc_files: []
19
47
  files:
20
48
  - ".gitignore"
49
+ - ".tool-versions"
50
+ - ".travis.sh"
21
51
  - ".travis.yml"
22
52
  - CODE_OF_CONDUCT.md
23
53
  - Gemfile
@@ -27,11 +57,16 @@ files:
27
57
  - bin/console
28
58
  - bin/setup
29
59
  - lib/micro/observers.rb
30
- - lib/micro/observers/events_or_actions.rb
60
+ - lib/micro/observers/events.rb
61
+ - lib/micro/observers/for/active_model.rb
62
+ - lib/micro/observers/for/active_record.rb
31
63
  - lib/micro/observers/manager.rb
32
64
  - lib/micro/observers/utils.rb
33
65
  - lib/micro/observers/version.rb
34
66
  - lib/u-observers.rb
67
+ - lib/u-observers/for/active_model.rb
68
+ - lib/u-observers/for/active_record.rb
69
+ - test.sh
35
70
  - u-observers.gemspec
36
71
  homepage: https://github.com/serradura/u-observers
37
72
  licenses:
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Micro
4
- module Observers
5
-
6
- module EventsOrActions
7
- DEFAULTS = [:call]
8
-
9
- def self.[](value)
10
- values = Utils.compact_array(value)
11
-
12
- values.empty? ? DEFAULTS : values
13
- end
14
-
15
- def self.fetch_actions(hash)
16
- return self[hash.fetch(:actions) { hash.fetch(:action) }] if hash.is_a?(Hash)
17
-
18
- raise ArgumentError, 'expected a hash with the key :action or :actions'
19
- end
20
-
21
- private_constant :DEFAULTS
22
- end
23
-
24
- end
25
- end