puremvc 1.0.0 → 1.0.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE +28 -0
  4. data/README.md +88 -0
  5. data/lib/core/controller.rb +177 -0
  6. data/lib/core/model.rb +133 -0
  7. data/lib/core/view.rb +230 -0
  8. data/lib/patterns/command/macro_command.rb +81 -0
  9. data/lib/patterns/command/simple_command.rb +28 -0
  10. data/lib/patterns/facade/facade.rb +267 -0
  11. data/lib/patterns/mediator/mediator.rb +56 -0
  12. data/lib/patterns/observer/notification.rb +68 -0
  13. data/lib/patterns/observer/notifier.rb +87 -0
  14. data/lib/patterns/observer/observer.rb +55 -0
  15. data/lib/patterns/proxy/proxy.rb +50 -0
  16. data/lib/puremvc.rb +19 -0
  17. data/sig/lib/core/controller.rbs +40 -0
  18. data/sig/lib/core/model.rbs +38 -0
  19. data/sig/lib/core/view.rbs +42 -0
  20. data/sig/lib/interfaces/i_command.rbs +18 -0
  21. data/sig/lib/interfaces/i_controller.rbs +52 -0
  22. data/sig/lib/interfaces/i_facade.rbs +116 -0
  23. data/sig/lib/interfaces/i_mediator.rbs +60 -0
  24. data/sig/lib/interfaces/i_model.rbs +38 -0
  25. data/sig/lib/interfaces/i_notification.rbs +52 -0
  26. data/sig/lib/interfaces/i_notifier.rbs +48 -0
  27. data/sig/lib/interfaces/i_observer.rbs +56 -0
  28. data/sig/lib/interfaces/i_proxy.rbs +36 -0
  29. data/sig/lib/interfaces/i_view.rbs +71 -0
  30. data/sig/lib/patterns/command/macro_command.rbs +18 -0
  31. data/sig/lib/patterns/command/simple_command.rbs +11 -0
  32. data/sig/lib/patterns/facade/facade.rbs +48 -0
  33. data/sig/lib/patterns/mediator/mediator.rbs +18 -0
  34. data/sig/lib/patterns/observer/notification.rbs +18 -0
  35. data/sig/lib/patterns/observer/notifier.rbs +17 -0
  36. data/sig/lib/patterns/observer/observer.rbs +20 -0
  37. data/sig/lib/patterns/proxy/proxy.rbs +19 -0
  38. metadata +71 -56
  39. data/puremvc.rb +0 -13
  40. data/src/org/puremvc/ruby/core/controller.rb +0 -76
  41. data/src/org/puremvc/ruby/core/model.rb +0 -57
  42. data/src/org/puremvc/ruby/core/view.rb +0 -112
  43. data/src/org/puremvc/ruby/patterns/command/macro_command.rb +0 -59
  44. data/src/org/puremvc/ruby/patterns/command/simple_command.rb +0 -17
  45. data/src/org/puremvc/ruby/patterns/facade/facade.rb +0 -161
  46. data/src/org/puremvc/ruby/patterns/mediator/mediator.rb +0 -37
  47. data/src/org/puremvc/ruby/patterns/observer/notification.rb +0 -38
  48. data/src/org/puremvc/ruby/patterns/observer/notifier.rb +0 -27
  49. data/src/org/puremvc/ruby/patterns/observer/observer.rb +0 -31
  50. data/src/org/puremvc/ruby/patterns/proxy/proxy.rb +0 -32
  51. data/version.txt +0 -12
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # macro_command.rb
5
+ # PureMVC Ruby Multicore
6
+ #
7
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
8
+ # Your reuse is governed by the BSD 3-Clause License
9
+
10
+ module PureMVC
11
+ # A base ICommand implementation that executes other ICommand instances.
12
+ #
13
+ # A MacroCommand maintains a list of ICommand class references called SubCommands.
14
+ #
15
+ # When <code>execute</code>is called, the MacroCommand instantiates and calls <code>execute</code>on each of its
16
+ # SubCommands in turn. Each SubCommand will be passed a reference to the original INotification that was passed to
17
+ # the MacroCommand's <code>execute</code>method.
18
+ #
19
+ # Unlike SimpleCommand, your subclass should not override <code>execute</code> but instead override
20
+ # <code>initialize_macro_command</code> calling <code>add_sub_command</code>once for each SubCommand to be executed.
21
+ #
22
+ # @see Controller
23
+ # @see Notification
24
+ # @see SimpleCommand
25
+ class MacroCommand < Notifier
26
+ # Constructor.
27
+ #
28
+ # You should not need to define a constructor,
29
+ # instead, override the <code>initialize_macro_command</code> method.
30
+ #
31
+ # If your subclass does define a constructor,
32
+ # be sure to call <code>super</code>
33
+ def initialize
34
+ super()
35
+ @sub_commands = []
36
+ initialize_macro_command
37
+ end
38
+
39
+ # Initialize the MacroCommand.
40
+ #
41
+ # In your subclass, override this method to
42
+ # initialize the MacroCommand's SubCommand list with
43
+ # ICommand factory references, like this:
44
+ #
45
+ # @example
46
+ # def initialize_macro_command
47
+ # add_sub_command { FirstCommand.new }
48
+ # add_sub_command { SecondCommand.new }
49
+ # add_sub_command { ThirdCommand.new }
50
+ # end
51
+ #
52
+ # Note that SubCommands may be any ICommand implementor;
53
+ # MacroCommands or SimpleCommands are both acceptable.
54
+ def initialize_macro_command; end
55
+
56
+ # Add a SubCommand.
57
+ # SubCommands will be called in First In/First Out (FIFO) order.
58
+ #
59
+ # @param factory [^() -> _ICommand] A block or callable that returns an instance of ICommand when called.
60
+ def add_sub_command(&factory)
61
+ @sub_commands << factory
62
+ end
63
+
64
+ # Execute this MacroCommand's SubCommands.
65
+ #
66
+ # The SubCommands will be called in First In/First Out (FIFO) order.
67
+ #
68
+ # @param notification [INotification] the notification object to be passed to each SubCommand.
69
+ def execute(notification)
70
+ while @sub_commands.any?
71
+ # @type factory: [^() -> ICommand]
72
+ factory = @sub_commands.shift
73
+
74
+ # @type var command: _ICommand
75
+ command = factory.call
76
+ command.initialize_notifier(@multiton_key)
77
+ command.execute(notification)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # simple_command.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base <code>ICommand</code>implementation.
11
+ #
12
+ # Subclasses should override the <code>execute</code>method, where business logic
13
+ # will handle the <code>INotification</code>.
14
+ #
15
+ # @see Controller
16
+ # @see Notification
17
+ # @see MacroCommand
18
+ class SimpleCommand < Notifier
19
+ # Fulfill the use-case initiated by the given <code>INotification</code>.
20
+ #
21
+ # In the Command Pattern, an application use-case typically begins with some user action,
22
+ # which results in an <code>INotification</code>being broadcast, handled by business logic in the
23
+ # <code>execute</code>method of an <code>ICommand</code>.
24
+ #
25
+ # @param notification [_INotification] the notification to handle
26
+ def execute(notification); end
27
+ end
28
+ end
@@ -0,0 +1,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ # facade.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base Multiton <code>IFacade</code> implementation.
11
+ #
12
+ # @see Model
13
+ # @see View
14
+ # @see Controller
15
+ class Facade
16
+ # Message Constants
17
+ MULTITON_MSG = 'Facade instance for this Multiton key already constructed!'
18
+ private_constant :MULTITON_MSG
19
+
20
+ class << self
21
+ # The Multiton IFacade instanceMap.
22
+ # @return [Hash{String => IFacade}]
23
+ def instance_map = (@instance_map ||= {})
24
+
25
+ # Mutex used to synchronize access to the instance map for thread safety.
26
+ # @return [Mutex]
27
+ def mutex = (@mutex ||= Mutex.new)
28
+
29
+ # Facade Multiton Factory method.
30
+ #
31
+ # @param key [String] the unique key identifying the Multiton instance
32
+ # @param factory [^(String) -> _IFacade] the unique key passed to the factory block
33
+ # @return [IFacade] the Multiton instance of the Facade
34
+ def get_instance(key, &factory)
35
+ mutex.synchronize do
36
+ instance_map[key] ||= factory.call(key)
37
+ end
38
+ end
39
+
40
+ # Check if a Core is registered or not.
41
+ #
42
+ # @param key [String] the multiton key for the Core in question
43
+ # @return [Boolean] whether a Core is registered with the given <code>key</code>.
44
+ def has_core?(key)
45
+ instance_map.key?(key)
46
+ end
47
+
48
+ # Remove a Core.
49
+ #
50
+ # Removes the Model, View, Controller, and Facade
51
+ # instances associated with the given key.
52
+ #
53
+ # @param key [String] the key of the Core to remove
54
+ def remove_core(key)
55
+ mutex.synchronize do
56
+ Model.remove_model(key)
57
+ View.remove_view(key)
58
+ Controller.remove_controller(key)
59
+ instance_map.delete(key)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Constructor.
65
+ #
66
+ # This <code>IFacade</code> implementation is a Multiton, so you should not call the constructor
67
+ # directly. Instead, use the static factory method and pass the unique key for this instance with factory:
68
+ # <code>PureMVC::Facade.get_instance(key) { |key| PureMVC::Facade.new(key) }</code>.
69
+ #
70
+ # @param key [String]
71
+ # @raise [RuntimeError] if an instance for this Multiton key has already been constructed.
72
+ def initialize(key)
73
+ raise MULTITON_MSG if self.class.instance_map[key]
74
+
75
+ self.class.instance_map[key] = self
76
+ @model = @view = @controller = nil
77
+ initialize_notifier(key)
78
+ initialize_facade
79
+ end
80
+
81
+ # Initialize the Multiton <code>Facade</code> instance.
82
+ #
83
+ # This method is called automatically by the constructor. Override it in your
84
+ # subclass to perform any subclass-specific initialization.
85
+ #
86
+ # @note Be sure to call <code>super.initialize_facade</code> when overriding.
87
+ def initialize_facade
88
+ initialize_model
89
+ initialize_controller
90
+ initialize_view
91
+ end
92
+
93
+ # Initialize the <code>Controller</code>.
94
+ #
95
+ # Called by the <code>initialize_facade</code> method.
96
+ #
97
+ # Override this method in your subclass of <code>Facade</code> if one or both of the following are true:
98
+ # - You wish to initialize a different <code>IController</code>.
99
+ # - You have <code>Commands</code> to register with the <code>Controller</code> at startup.
100
+ #
101
+ # If you don't want to initialize a different<code>IController</code>,call<code>super.initialize_controller()</code>
102
+ # at the beginning of your method, then register <code>Command</code>s.
103
+ def initialize_controller
104
+ @controller = Controller.get_instance(@multiton_key) { |key| Controller.new(key) }
105
+ end
106
+
107
+ # Initialize the <code>Model</code>.
108
+ #
109
+ # Called by the <code>initializeFacade</code> method.
110
+ #
111
+ # Override this method in your subclass of <code>Facade</code> if one or both of the following are true:
112
+ # - You wish to initialize a different <code>IModel</code>.
113
+ # - You have <code>Proxy</code>s to register with the Model that do not retrieve a reference to the
114
+ # <code>Facade</code> at construction time.
115
+ #
116
+ # If you don't want to initialize a different <code>IModel</code>, call <code>super.initialize_model()</code> at
117
+ # the beginning of your method, then register <code>Proxy</code>s.
118
+ #
119
+ # Note: This method is <i>rarely</i> overridden; in practice you are more likely to use a <code>Command</code> to
120
+ # create and register <code>Proxy</code>s with the <code>Model</code>, since <code>Proxy</code>s with mutable data
121
+ # will likely need to send <code>INotification</code>s and thus will likely want to fetch a reference to the
122
+ # <code>Facade</code> during their construction.
123
+ def initialize_model
124
+ @model = Model.get_instance(@multiton_key) { |key| Model.new(key) }
125
+ end
126
+
127
+ # Initialize the <code>View</code>.
128
+ #
129
+ # Called by the <code>initializeFacade</code> method.
130
+ #
131
+ # Override this method in your subclass of <code>Facade</code> if one or both of the following are true:
132
+ # - You wish to initialize a different <code>IView</code>.
133
+ # - You have <code>Observers</code> to register with the <code>View</code>.
134
+ #
135
+ # If you don't want to initialize a different <code>IView</code>, call <code>super.initialize_view()</code> at the
136
+ # beginning of your method, then register <code>IMediator</code> instances.
137
+ #
138
+ # Note: This method is <i>rarely</i> overridden; in practice you are more likely to use a <code>Command</code> to
139
+ # create and register <code>Mediator</code>s with the <code>View</code>, since <code>IMediator</code> instances will
140
+ # need to send <code>INotification</code>s and thus will likely want to fetch a reference to the <code>Facade</code>
141
+ # during their construction.
142
+ def initialize_view
143
+ @view = View.get_instance(@multiton_key) { |key| View.new(key) }
144
+ end
145
+
146
+ # Register an <code>ICommand</code> with the <code>Controller</code> by Notification name.
147
+ #
148
+ # @param notification_name [String] name of the <code>INotification</code> to associate with <code>ICommand</code>
149
+ # @param factory [^<() -> ICommand>] a reference to the Class of the <code>ICommand</code>
150
+ def register_command(notification_name, &factory)
151
+ @controller&.register_command(notification_name, &factory)
152
+ end
153
+
154
+ # Check if a Command is registered for a given Notification
155
+ #
156
+ # @param notification_name [String] The name of the Notification to check
157
+ # @return [Boolean] whether a Command is currently registered for the given <code>notification_name</code>.
158
+ def has_command?(notification_name)
159
+ !!@controller&.has_command?(notification_name)
160
+ end
161
+
162
+ # Remove a previously registered <code>ICommand</code> to <code>INotification</code> mapping from the Controller.
163
+ #
164
+ # @param notification_name [String] the name of the <code>INotification</code> to remove the <code>ICommand</code>
165
+ def remove_command(notification_name)
166
+ @controller&.remove_command(notification_name)
167
+ end
168
+
169
+ # Register an <code>IProxy</code> with the <code>Model</code> by name.
170
+ #
171
+ # @param proxy [IProxy] the <code>IProxy</code> instance to be registered with the <code>Model</code>.
172
+ def register_proxy(proxy)
173
+ @model&.register_proxy(proxy)
174
+ end
175
+
176
+ # Retrieve an <code>IProxy</code> from the <code>Model</code> by name.
177
+ #
178
+ # @param proxy_name [String] the name of the proxy to be retrieved.
179
+ # @return [IProxy, nil] the <code>IProxy</code> instance previously registered with the <code>proxy_name</code>
180
+ def retrieve_proxy(proxy_name)
181
+ @model&.retrieve_proxy(proxy_name)
182
+ end
183
+
184
+ # Check if a Proxy is registered
185
+ #
186
+ # @param proxy_name [String] the name of the Proxy
187
+ # @return [Boolean] whether a Proxy is currently registered with the given <code>proxyName</code>.
188
+ def has_proxy?(proxy_name)
189
+ !!@model&.has_proxy?(proxy_name)
190
+ end
191
+
192
+ # Remove an <code>IProxy</code> from the <code>Model</code> by name.
193
+ #
194
+ # @param proxy_name [String] the <code>IProxy</code> to remove from the <code>Model</code>.
195
+ # @return [IProxy, nil] the <code>IProxy</code> that was removed from the <code>Model</code>, or nil
196
+ def remove_proxy(proxy_name)
197
+ @model&.remove_proxy(proxy_name)
198
+ end
199
+
200
+ # Register an <code>IMediator</code> with the <code>View</code>.
201
+ #
202
+ # @param mediator [IMediator] a reference to the <code>IMediator</code>
203
+ def register_mediator(mediator)
204
+ @view&.register_mediator(mediator)
205
+ end
206
+
207
+ # Retrieve an <code>IMediator</code> from the <code>View</code>.
208
+ #
209
+ # @param mediator_name [String] the name of the <code>IMediator</code> to retrieve
210
+ # @return [IMediator, nil] the <code>IMediator</code> previously registered with the <code>mediator_name</code>
211
+ def retrieve_mediator(mediator_name)
212
+ @view&.retrieve_mediator(mediator_name)
213
+ end
214
+
215
+ # Check if a Mediator is registered or not
216
+ #
217
+ # @param mediator_name [String] the name of the Mediator
218
+ # @return [Boolean] whether a Mediator is registered with the given <code>mediator_name</code>.
219
+ def has_mediator?(mediator_name)
220
+ !!@view&.has_mediator?(mediator_name)
221
+ end
222
+
223
+ # Remove an <code>IMediator</code> from the <code>View</code>.
224
+ #
225
+ # @param mediator_name [String] name of the <code>IMediator</code> to be removed.
226
+ # @return [IMediator, nil] the <code>IMediator</code> that was removed from the <code>View</code>, or nil
227
+ def remove_mediator(mediator_name)
228
+ @view&.remove_mediator(mediator_name)
229
+ end
230
+
231
+ # Notify <code>Observer</code>s.
232
+ #
233
+ # This method is left public mostly for backward compatibility
234
+ # and to allow you to send custom notification classes using the facade.
235
+ #
236
+ # Usually you should call <code>send_notification</code>
237
+ # and pass the parameters, never having to
238
+ # construct the notification yourself.
239
+ #
240
+ # @param notification [INotification] the notification to have the <code>View</code> notify <code>Observers</code>.
241
+ def notify_observers(notification)
242
+ @view&.notify_observers(notification)
243
+ end
244
+
245
+ # Create and send an <code>INotification</code>.
246
+ #
247
+ # Keeps us from having to construct new notification instances in our implementation code.
248
+ #
249
+ # @param name [String] the name of the notification to send
250
+ # @param body [Object, nil] the body of the notification (optional)
251
+ # @param type [String, nil] the type of the notification (optional)
252
+ def send_notification(name, body = nil, type = nil)
253
+ notify_observers(Notification.new(name, body, type))
254
+ end
255
+
256
+ # Set the Multiton key for this facade instance.
257
+ #
258
+ # This is not meant to be called directly. It is invoked internally by the
259
+ # constructor when <code>get_instance</code> is called. However, it must be public
260
+ # to implement <code>INotifier</code>.
261
+ #
262
+ # @param key [String] the multiton key for this instance
263
+ def initialize_notifier(key)
264
+ @multiton_key = key
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # mediator.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base <code>IMediator</code> implementation.
11
+ #
12
+ # @see View
13
+ class Mediator < Notifier
14
+ # The name of the <code>Mediator</code>.
15
+ #
16
+ # Typically, a <code>Mediator</code> will be written to serve
17
+ # one specific control or group controls and so,
18
+ # will not have a need to be dynamically named.
19
+ NAME = 'Mediator'
20
+ public_constant :NAME
21
+
22
+ # @return [String] The name of the Mediator.
23
+ attr_reader :name
24
+
25
+ # @return [Object, nil] The component associated with this Mediator.
26
+ attr_accessor :component
27
+
28
+ # Initializes a new Mediator instance.
29
+ #
30
+ # @param name [String | nil] the name of the mediator
31
+ # @param component [Object, nil] the component this mediator manages
32
+ def initialize(name = nil, component = nil)
33
+ super()
34
+ @name = name || NAME
35
+ @component = component
36
+ end
37
+
38
+ # Returns an array of notification names this mediator is interested in.
39
+ #
40
+ # @return [Array<String>] list of notification names
41
+ def list_notification_interests
42
+ []
43
+ end
44
+
45
+ # Handles a notification.
46
+ #
47
+ # @param notification [_INotification] the notification to handle
48
+ def handle_notification(notification); end
49
+
50
+ # Called when the mediator is registered.
51
+ def on_register; end
52
+
53
+ # Called when the mediator is removed.
54
+ def on_remove; end
55
+ end
56
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # notification.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base <code>INotification</code>implementation.
11
+ #
12
+ # PureMVC does not rely upon underlying event models such
13
+ # as the one provided with Flash, and ActionScript 3 does
14
+ # not have an inherent event model.
15
+ #
16
+ # The Observer Pattern as implemented within PureMVC exists
17
+ # to support event-driven communication between the
18
+ # application and the actors of the MVC triad.
19
+ #
20
+ # Notifications are not meant to be a replacement for Events
21
+ # in Flex/Flash/Apollo. Generally, <code>IMediator</code>implementors
22
+ # place event listeners on their view components, which they
23
+ # then handle in the usual way. This may lead to the broadcast of <code>Notification</code>s to
24
+ # trigger <code>ICommand</code>s or to communicate with other <code>IMediator</code>s. <code>IProxy</code> and
25
+ # <code>ICommand</code> instances communicate with each other and <code>IMediator</code>s
26
+ # by broadcasting <code>INotification</code>s.
27
+ #
28
+ # A key difference between Flash <code>Event</code>s and PureMVC
29
+ # <code>Notification</code>s is that <code>Event</code>s follow the
30
+ # 'Chain of Responsibility' pattern, 'bubbling' up the display hierarchy
31
+ # until some parent component handles the <code>Event</code>, while
32
+ # PureMVC <code>Notification</code>s follow a 'Publish/Subscribe'
33
+ # pattern. PureMVC classes need not be related to each other in a
34
+ # parent/child relationship to communicate with one another
35
+ # using <code>Notification</code>s.
36
+ #
37
+ # @see Observer
38
+ class Notification
39
+ # @return [String] the name of the notification
40
+ attr_reader :name
41
+
42
+ # @return [Object, nil] the body of the notification
43
+ attr_accessor :body
44
+
45
+ # @return [String, nil] the type of the notification
46
+ attr_accessor :type
47
+
48
+ # The Notification class represents a message with a name, optional body, and optional type.
49
+ #
50
+ # @param name [String] the name of the notification
51
+ # @param body [Object, nil] optional data to pass with the notification
52
+ # @param type [String, nil] optional type identifier
53
+ def initialize(name, body = nil, type = nil)
54
+ @name = name
55
+ @body = body
56
+ @type = type
57
+ end
58
+
59
+ # Returns a string representation of the notification.
60
+ #
61
+ # @return [String]
62
+ def to_s
63
+ "Notification Name: #{@name}" \
64
+ "\nBody: #{@body.inspect}" \
65
+ "\nType: #{@type}"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # notifier.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base <code>INotifier</code> implementation.
11
+ #
12
+ # <code>MacroCommand</code>, <code>SimpleCommand</code>, <code>Mediator</code>, and <code>Proxy</code>
13
+ # all need to send <code>Notification</code>s.
14
+ #
15
+ # The <code>INotifier</code> interface provides a common method called
16
+ # <code>send_notification</code> that relieves implementation code of
17
+ # the necessity to actually construct <code>Notification</code>s.
18
+ #
19
+ # The <code>Notifier</code> class, which all the above-mentioned classes
20
+ # extend, provides an initialized reference to the <code>Facade</code>
21
+ # Multiton, which is required for the convenience method
22
+ # for sending <code>Notification</code>s. It also eases implementation,
23
+ # as these classes have frequent <code>Facade</code> interactions and
24
+ # usually require access to it anyway.
25
+ #
26
+ # NOTE: In the MultiCore version of the framework, there is one caveat:
27
+ # notifiers cannot send notifications or reach the facade until they
28
+ # have a valid <code>multiton_key</code>.
29
+ #
30
+ # The <code>multiton_key</code> is set:
31
+ # * on a <code>SimpleCommand</code> when it is executed by the <code>Controller<c/ode>
32
+ # * on a <code>Mediator</code> when registered with the <code>View</code>
33
+ # * on a <code>Proxy</code> when registered with the <code>Model</code>
34
+ #
35
+ # @see Proxy
36
+ # @see Facade
37
+ # @see Mediator
38
+ # @see MacroCommand
39
+ # @see SimpleCommand
40
+ class Notifier
41
+ # Message Constants
42
+ MULTITON_MSG = 'multitonKey for this Notifier not yet initialized!'
43
+ private_constant :MULTITON_MSG
44
+
45
+ # @attr_reader [String] The Multiton Key for this app
46
+ attr_reader :multiton_key
47
+
48
+ # Initialize this INotifier instance.
49
+ #
50
+ # This is how a Notifier receives its <code>multiton_key</code>.
51
+ # Any calls to <code>send_notification</code> or attempts to access the <code>facade</code>
52
+ # will fail until this method has been called.
53
+ #
54
+ # Subclasses such as <code>Mediator</code>, <code>Command</code>, or <code>Proxy</code> may override this
55
+ # method if they need to send notifications or access the <code>Facade</code> instance
56
+ # as early as possible. However, note that the <code>Facade</code> cannot be accessed
57
+ # within the constructor of these classes, because <code>initialize_notifier</code>
58
+ # will not yet have been called at that point.
59
+ #
60
+ # @param key [String] the <code>multiton_key</code> this <code>INotifier</code> will use
61
+ def initialize_notifier(key)
62
+ @multiton_key = key
63
+ end
64
+
65
+ # Create and send an INotification.
66
+ #
67
+ # This method eliminates the need to manually construct
68
+ # INotification instances in your implementation code.
69
+ #
70
+ # @param name [String] the name of the notification
71
+ # @param body [Object, nil] optional body
72
+ # @param type [String, nil] optional type
73
+ def send_notification(name, body = nil, type = nil)
74
+ facade.send_notification(name, body, type)
75
+ end
76
+
77
+ # Return the Multiton Facade instance
78
+ #
79
+ # @raise [RuntimeError] if the <code>multiton_key</code> is not set
80
+ # @return [IFacade] the facade instance for the notifier's key
81
+ def facade
82
+ raise MULTITON_MSG if @multiton_key.nil?
83
+
84
+ Facade.get_instance(@multiton_key) { |key| Facade.new(key) }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # observer.rb
4
+ # PureMVC Ruby Multicore
5
+ #
6
+ # Copyright(c) 2025 Saad Shams <saad.shams@puremvc.org>
7
+ # Your reuse is governed by the BSD 3-Clause License
8
+
9
+ module PureMVC
10
+ # A base <code>IObserver</code> implementation.
11
+ #
12
+ # An <code>Observer</code> is an object that encapsulates information
13
+ # about an interested object with a method that should
14
+ # be called when a particular <code>INotification</code> is broadcast.
15
+ #
16
+ # In PureMVC, the <code>Observer</code> class assumes these responsibilities:
17
+ # - Encapsulate the notification (callback) method of the interested object.
18
+ # - Encapsulate the notification context (<code>self</code>) of the interested object.
19
+ # - Provide methods for setting the notification method and context.
20
+ # - Provide a method for notifying the interested object.
21
+ #
22
+ # @see View
23
+ # @see Notification
24
+ class Observer
25
+ # @return [Method | nil] notify The callback method to be called on notification.
26
+ attr_accessor :notify
27
+
28
+ # @return [Object | nil] context The object context for the callback.
29
+ attr_accessor :context
30
+
31
+ # Initialize an Observer with a notify method and context.
32
+ #
33
+ # @param notify [Method, nil] the callback method to invoke on notification.
34
+ # @param context [Object, nil] the object context for the callback.
35
+ def initialize(notify = nil, context = nil)
36
+ @notify = notify
37
+ @context = context
38
+ end
39
+
40
+ # Calls the notify method with the given notification.
41
+ #
42
+ # @param notification [INotification] the notification to send.
43
+ def notify_observer(notification)
44
+ @notify&.call(notification)
45
+ end
46
+
47
+ # Compares the given object with the Observer's context.
48
+ #
49
+ # @param object [Object] the object to compare.
50
+ # @return [Boolean] true if the given object is the same as the context.
51
+ def compare_notify_context?(object)
52
+ object.equal?(@context)
53
+ end
54
+ end
55
+ end