u-observers 0.8.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,31 +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_or_actions'
7
- require 'micro/observers/manager'
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
6
+ require 'micro/observers/event'
7
+ require 'micro/observers/subscribers'
8
+ require 'micro/observers/broadcast'
9
+ require 'micro/observers/set'
27
10
 
28
11
  def observers
29
- @observers ||= Observers::Manager.for(self)
12
+ @__observers ||= Observers::Set.for(self)
30
13
  end
31
-
32
14
  end
33
15
  end
@@ -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
@@ -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