u-observers 0.9.0 → 2.2.1

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.
@@ -3,30 +3,13 @@ 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'
8
-
9
- module ClassMethods
10
- def notify_observers!(events)
11
- proc do |object|
12
- object.observers.subject_changed!
13
- object.observers.send(:notify!, events)
14
- end
15
- end
16
-
17
- def notify_observers(*events)
18
- notify_observers!(Events[events])
19
- end
20
- end
21
-
22
- def self.included(base)
23
- base.extend(ClassMethods)
24
- base.send(:private_class_method, :notify_observers!)
25
- end
6
+ require 'micro/observers/event'
7
+ require 'micro/observers/subscribers'
8
+ require 'micro/observers/broadcast'
9
+ require 'micro/observers/set'
26
10
 
27
11
  def observers
28
- @observers ||= Observers::Manager.for(self)
12
+ @__observers ||= Observers::Set.for(self)
29
13
  end
30
-
31
14
  end
32
15
  end
@@ -0,0 +1,77 @@
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
+ if with && !with.is_a?(Proc)
60
+ callable.call(with)
61
+
62
+ return true
63
+ end
64
+
65
+ event = Event.new(event_name, subject, context, data)
66
+
67
+ callable.call(with.is_a?(Proc) ? with.call(event) : event)
68
+
69
+ true
70
+ end
71
+
72
+ private_constant :BroadcastWith, :NotifySubscriber, :NotifyCallable, :NotifyObserver
73
+ end
74
+
75
+ private_constant :Broadcast
76
+ end
77
+ 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
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+
6
+ class Event::Names
7
+ EMPTY_ARRAY = [].freeze
8
+
9
+ def self.[](value, default: EMPTY_ARRAY)
10
+ values = Utils::Arrays.flatten_and_compact(value)
11
+
12
+ values.empty? ? default : values
13
+ end
14
+
15
+ NO_EVENTS_MSG = 'no events (expected at least 1)'.freeze
16
+
17
+ def self.fetch(value)
18
+ values = self[value]
19
+
20
+ return values unless values.empty?
21
+
22
+ raise ArgumentError, NO_EVENTS_MSG
23
+ end
24
+
25
+ private_constant :NO_EVENTS_MSG
26
+ end
27
+
28
+ end
29
+ 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!(Event::Names.fetch(events))
18
+ end
19
+
20
+ def notify_observers_on(*callback_methods)
21
+ Utils::Arrays.fetch_from_args(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
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Observers
5
+
6
+ class Set
7
+ def self.for(subject)
8
+ new(subject)
9
+ end
10
+
11
+ def initialize(subject, subscribers: nil)
12
+ @subject = subject
13
+ @subject_changed = false
14
+ @subscribers = Subscribers.new(subscribers)
15
+ end
16
+
17
+ def count; @subscribers.count; end
18
+ def none?; @subscribers.none?; end
19
+ def some?; !none?; end
20
+
21
+ def subject_changed?
22
+ @subject_changed
23
+ end
24
+
25
+ INVALID_BOOLEAN_MSG = 'expected a boolean (true, false)'.freeze
26
+
27
+ def subject_changed(state)
28
+ return @subject_changed = state if state == true || state == false
29
+
30
+ raise ArgumentError, INVALID_BOOLEAN_MSG
31
+ end
32
+
33
+ def subject_changed!
34
+ subject_changed(true)
35
+ end
36
+
37
+ def include?(observer)
38
+ @subscribers.include?(observer)
39
+ end
40
+ alias included? include?
41
+
42
+ def attach(*args); @subscribers.attach(args) and self; end
43
+ def detach(*args); @subscribers.detach(args) and self; end
44
+
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
47
+
48
+ def off(*args)
49
+ @subscribers.off(args) and self
50
+ end
51
+
52
+ def notify(*events, data: nil)
53
+ broadcast_if_subject_changed(Event::Names.fetch(events), data)
54
+ end
55
+
56
+ def notify!(*events, data: nil)
57
+ broadcast(Event::Names.fetch(events), data)
58
+ end
59
+
60
+ CALL_EVENT = [:call].freeze
61
+
62
+ def call(*events, data: nil)
63
+ broadcast_if_subject_changed(Event::Names[events, default: CALL_EVENT], data)
64
+ end
65
+
66
+ def call!(*events, data: nil)
67
+ broadcast(Event::Names[events, default: CALL_EVENT], data)
68
+ end
69
+
70
+ def inspect
71
+ subs = @subscribers.to_inspect
72
+
73
+ '#<%s @subject=%s @subject_changed=%p @subscribers=%p>' % [self.class, @subject, @subject_changed, subs]
74
+ end
75
+
76
+ private
77
+
78
+ def broadcast_if_subject_changed(event_names, data = nil)
79
+ return self if none? || !subject_changed?
80
+
81
+ broadcast(event_names, data)
82
+
83
+ subject_changed(false)
84
+
85
+ self
86
+ end
87
+
88
+ def broadcast(event_names, data)
89
+ return self if none?
90
+
91
+ Broadcast.call(@subscribers, @subject, data, event_names)
92
+
93
+ self
94
+ end
95
+
96
+ private_constant :INVALID_BOOLEAN_MSG, :CALL_EVENT
97
+ end
98
+
99
+ end
100
+ 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
@@ -6,8 +6,18 @@ module Micro
6
6
  module Utils
7
7
  EMPTY_HASH = {}.freeze
8
8
 
9
- def self.compact_array(value)
10
- Array(value).flatten.tap(&:compact!)
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
+
14
+ def self.flatten_and_compact(value)
15
+ return [] unless value
16
+
17
+ array = Array(value).flatten
18
+ array.compact!
19
+ array
20
+ end
11
21
  end
12
22
  end
13
23