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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE +28 -0
- data/README.md +88 -0
- data/lib/core/controller.rb +177 -0
- data/lib/core/model.rb +133 -0
- data/lib/core/view.rb +230 -0
- data/lib/patterns/command/macro_command.rb +81 -0
- data/lib/patterns/command/simple_command.rb +28 -0
- data/lib/patterns/facade/facade.rb +267 -0
- data/lib/patterns/mediator/mediator.rb +56 -0
- data/lib/patterns/observer/notification.rb +68 -0
- data/lib/patterns/observer/notifier.rb +87 -0
- data/lib/patterns/observer/observer.rb +55 -0
- data/lib/patterns/proxy/proxy.rb +50 -0
- data/lib/puremvc.rb +19 -0
- data/sig/lib/core/controller.rbs +40 -0
- data/sig/lib/core/model.rbs +38 -0
- data/sig/lib/core/view.rbs +42 -0
- data/sig/lib/interfaces/i_command.rbs +18 -0
- data/sig/lib/interfaces/i_controller.rbs +52 -0
- data/sig/lib/interfaces/i_facade.rbs +116 -0
- data/sig/lib/interfaces/i_mediator.rbs +60 -0
- data/sig/lib/interfaces/i_model.rbs +38 -0
- data/sig/lib/interfaces/i_notification.rbs +52 -0
- data/sig/lib/interfaces/i_notifier.rbs +48 -0
- data/sig/lib/interfaces/i_observer.rbs +56 -0
- data/sig/lib/interfaces/i_proxy.rbs +36 -0
- data/sig/lib/interfaces/i_view.rbs +71 -0
- data/sig/lib/patterns/command/macro_command.rbs +18 -0
- data/sig/lib/patterns/command/simple_command.rbs +11 -0
- data/sig/lib/patterns/facade/facade.rbs +48 -0
- data/sig/lib/patterns/mediator/mediator.rbs +18 -0
- data/sig/lib/patterns/observer/notification.rbs +18 -0
- data/sig/lib/patterns/observer/notifier.rbs +17 -0
- data/sig/lib/patterns/observer/observer.rbs +20 -0
- data/sig/lib/patterns/proxy/proxy.rbs +19 -0
- metadata +71 -56
- data/puremvc.rb +0 -13
- data/src/org/puremvc/ruby/core/controller.rb +0 -76
- data/src/org/puremvc/ruby/core/model.rb +0 -57
- data/src/org/puremvc/ruby/core/view.rb +0 -112
- data/src/org/puremvc/ruby/patterns/command/macro_command.rb +0 -59
- data/src/org/puremvc/ruby/patterns/command/simple_command.rb +0 -17
- data/src/org/puremvc/ruby/patterns/facade/facade.rb +0 -161
- data/src/org/puremvc/ruby/patterns/mediator/mediator.rb +0 -37
- data/src/org/puremvc/ruby/patterns/observer/notification.rb +0 -38
- data/src/org/puremvc/ruby/patterns/observer/notifier.rb +0 -27
- data/src/org/puremvc/ruby/patterns/observer/observer.rb +0 -31
- data/src/org/puremvc/ruby/patterns/proxy/proxy.rb +0 -32
- data/version.txt +0 -12
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 66bcb05b4710e85999ef1b4d7e223e3269a94b9acc9ef256a046069cf7bc6402
|
4
|
+
data.tar.gz: eac762df289443442fc67ac7a5d54992f5ea73406fb9916d2909a984f98e2c13
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 718373aa1bf508d2ebfd0bf575abb396741c30a4eebd62dfd87deef35cb2678f9fe460927019962e10f2872a3e672ce3469e124c038d71b829a17f295757c4df
|
7
|
+
data.tar.gz: 3e748babb7ac73b47b8e60f354300d11af2dac7ff4bf57c06f8598c0ff11111e1a156e53b8ec7d2374eb3d8b67dcfa807907278ac8ca04bfbed732cff4aaac26
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
## [1.0.1] - 2025-08-14
|
6
|
+
### Changed
|
7
|
+
- Bumped version to 1.0.1 to allow Multicore override after initial 1.0.0 standard release on rubygems.org
|
8
|
+
|
9
|
+
## [1.0.0] - 2025-08-14
|
10
|
+
### Added
|
11
|
+
- Initial release of the framework
|
data/LICENSE
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
BSD 3-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2025, Saad Shams <saad.shams@puremvc.org>
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
16
|
+
contributors may be used to endorse or promote products derived from
|
17
|
+
this software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
## [PureMVC](http://puremvc.github.com/) Ruby MultiCore Framework [](https://github.com/PureMVC/puremvc-ruby-multicore-framework/actions/workflows/ruby.yml)
|
2
|
+
|
3
|
+
PureMVC is a lightweight framework for creating applications based upon the classic [Model-View-Controller](http://en.wikipedia.org/wiki/Model-view-controller) design meta-pattern. It supports [modular programming](http://en.wikipedia.org/wiki/Modular_programming) through the use of [Multiton](http://en.wikipedia.org/wiki/Multiton) Core actors instead of the [Singletons](http://en.wikipedia.org/wiki/Singleton_pattern) used in the [Standard](https://github.com/PureMVC/puremvc-ruby-standard-framework/wiki) Version.
|
4
|
+
|
5
|
+
* [Ruby Gem](https://rubygems.org/gems/puremvc)
|
6
|
+
* [API Docs](http://puremvc.org/pages/docs/Ruby/multicore/)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
```shell
|
10
|
+
gem install puremvc
|
11
|
+
```
|
12
|
+
|
13
|
+
## Platforms / Technologies
|
14
|
+
* [Ruby](https://en.wikipedia.org/wiki/Ruby_(programming_language))
|
15
|
+
* [Command-line interface](https://en.wikipedia.org/wiki/Command-line_interface)
|
16
|
+
* [Cross-platform software](https://en.wikipedia.org/wiki/Cross-platform_software)
|
17
|
+
|
18
|
+
---
|
19
|
+
## Development
|
20
|
+
|
21
|
+
### Install Dependencies
|
22
|
+
```shell
|
23
|
+
bundle install
|
24
|
+
```
|
25
|
+
|
26
|
+
### Lint Code
|
27
|
+
```shell
|
28
|
+
bundle exec rubocop
|
29
|
+
```
|
30
|
+
|
31
|
+
### Generate RBS Signatures
|
32
|
+
```shell
|
33
|
+
rbs prototype rb lib/**/*.rb --out-dir=sig
|
34
|
+
```
|
35
|
+
|
36
|
+
### Run Type Checking
|
37
|
+
```shell
|
38
|
+
steep check
|
39
|
+
```
|
40
|
+
|
41
|
+
### Test
|
42
|
+
```shell
|
43
|
+
ruby -Itest -e 'Dir["test/**/*_test.rb"].each { |file| require_relative file }'
|
44
|
+
RubyMine: Test file name mask: **/{*_test,test_*,*_spec}.rb
|
45
|
+
```
|
46
|
+
|
47
|
+
### Generate Documentation
|
48
|
+
```shell
|
49
|
+
yard doc lib/**/*.rb --protected --private
|
50
|
+
open doc/index.html
|
51
|
+
```
|
52
|
+
|
53
|
+
#### Publish
|
54
|
+
##### 1. Setup RubyGems API Key
|
55
|
+
```shell
|
56
|
+
mkdir -p ~/.gem
|
57
|
+
cat > ~/.gem/credentials <<EOF
|
58
|
+
---
|
59
|
+
:rubygems_api_key: API_KEY
|
60
|
+
EOF
|
61
|
+
chmod 0600 ~/.gem/credentials
|
62
|
+
```
|
63
|
+
##### 2. Build the gem
|
64
|
+
```shell
|
65
|
+
gem build puremvc.gemspec
|
66
|
+
gem push puremvc-1.0.0.gem
|
67
|
+
```
|
68
|
+
|
69
|
+
## Reference
|
70
|
+
* [Ruby](https://www.ruby-lang.org/en)
|
71
|
+
* [RBS](https://github.com/ruby/rbs)
|
72
|
+
* [YARD: Yay! A Ruby Documentation Tool](https://rubydoc.info/gems/yard)
|
73
|
+
|
74
|
+
---
|
75
|
+
|
76
|
+
## License
|
77
|
+
* PureMVC MultiCore Framework for Ruby
|
78
|
+
* PureMVC - Copyright © 2025 [Saad Shams](https://www.linkedin.com/in/muizz/)
|
79
|
+
* PureMVC - Copyright © 2025 [Futurescale, Inc.](http://futurescale.com/)
|
80
|
+
* All rights reserved.
|
81
|
+
|
82
|
+
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
83
|
+
|
84
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
85
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
86
|
+
* Neither the name of Futurescale, Inc., PureMVC.org, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
87
|
+
|
88
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# controller.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 Multiton <code>IController</code> implementation.
|
12
|
+
#
|
13
|
+
# In PureMVC, the <code>Controller</code> class follows the
|
14
|
+
# 'Command and Controller' strategy and assumes these responsibilities:
|
15
|
+
#
|
16
|
+
# * Remembering which <code>ICommand</code>s are intended to handle which <code>INotifications</code>.
|
17
|
+
# * Registering itself as an <code>IObserver</code> with the <code>View</code> for each <code>INotification</code>
|
18
|
+
# that has an <code>ICommand</code> mapping.
|
19
|
+
# * Creating a new instance of the proper <code>ICommand</code> to handle a given <code>INotification</code>
|
20
|
+
# when notified by the <code>View</code>.
|
21
|
+
# * Calling the <code>ICommand</code>'s <code>execute</code> method, passing in the <code>INotification</code>.
|
22
|
+
#
|
23
|
+
# Your application must register <code>ICommands</code> with the <code>Controller</code>.
|
24
|
+
#
|
25
|
+
# The simplest way is to subclass <code>Facade</code>, and use its <code>initializeController</code> method.
|
26
|
+
# to add your registrations.
|
27
|
+
#
|
28
|
+
# @see View
|
29
|
+
# @see Observer
|
30
|
+
# @see Notification
|
31
|
+
# @see SimpleCommand
|
32
|
+
# @see MacroCommand
|
33
|
+
class Controller
|
34
|
+
# Message Constants
|
35
|
+
MULTITON_MSG = 'Controller instance for this Multiton key already constructed!'
|
36
|
+
private_constant :MULTITON_MSG
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# The Multiton IController instanceMap.
|
40
|
+
# @return [Hash{String => IController}]
|
41
|
+
def instance_map = (@instance_map ||= {})
|
42
|
+
|
43
|
+
# Mutex used to synchronize access to the instance map for thread safety.
|
44
|
+
# @return [Mutex]
|
45
|
+
def mutex = (@mutex ||= Mutex.new)
|
46
|
+
|
47
|
+
# Gets an instance using the provided factory block
|
48
|
+
#
|
49
|
+
# @param key [String] the unique key identifying the Multiton instance
|
50
|
+
# @param factory [^(String) -> _IController] the unique key passed to the factory block
|
51
|
+
# @return [IController] The controller instance created by the factory
|
52
|
+
def get_instance(key, &factory)
|
53
|
+
mutex.synchronize do
|
54
|
+
instance_map[key] ||= factory.call(key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Remove an IController instance
|
59
|
+
#
|
60
|
+
# @param key [String] the multiton key of the IController instance to remove
|
61
|
+
def remove_controller(key)
|
62
|
+
mutex.synchronize do
|
63
|
+
instance_map.delete(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Constructor.
|
69
|
+
#
|
70
|
+
# This IController implementation is a Multiton, so you should not call the constructor
|
71
|
+
# directly. Instead, call the static factory method, passing the unique key for this instance:
|
72
|
+
# <code>PureMVC::Controller.getInstance(key) { |key| PureMVC::Controller.new(key) }</code>
|
73
|
+
#
|
74
|
+
# @param key [String]
|
75
|
+
# @raise [RuntimeError] if an instance for this Multiton key has already been constructed
|
76
|
+
def initialize(key)
|
77
|
+
raise MULTITON_MSG if self.class.instance_map[key]
|
78
|
+
|
79
|
+
self.class.instance_map[key] = self
|
80
|
+
# The Multiton Key for this Core
|
81
|
+
# @type var multiton_key: String
|
82
|
+
@multiton_key = key
|
83
|
+
# Local reference to View
|
84
|
+
# @type var component: _IView?
|
85
|
+
@view = nil
|
86
|
+
# Mapping of Notification names to Command factories
|
87
|
+
# @type var command_map: Hash[String, ^() -> _ICommand]
|
88
|
+
@command_map = {}
|
89
|
+
# Mutex used to synchronize access to the @command_map
|
90
|
+
# @type var command_mutex: Mutex
|
91
|
+
@command_mutex = Mutex.new
|
92
|
+
initialize_controller
|
93
|
+
end
|
94
|
+
|
95
|
+
# Initialize the Multiton Controller instance.
|
96
|
+
#
|
97
|
+
# Called automatically by the constructor.
|
98
|
+
#
|
99
|
+
# Note that if you are using a subclass of View
|
100
|
+
# in your application, you should also subclass Controller
|
101
|
+
# and override the initialize_controller method in the
|
102
|
+
# following way:
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# # ensure that the Controller is talking to my IView implementation
|
106
|
+
# def initialize_controller
|
107
|
+
# @view = MyView::get_instance(key) { |key| MyView.new(key) }
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
def initialize_controller
|
111
|
+
@view = View.get_instance(@multiton_key) { |key| View.new(key) }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Register a particular ICommand class as the handler for a particular INotification.
|
115
|
+
#
|
116
|
+
# If an ICommand has already been registered to handle INotifications with this name,
|
117
|
+
# it is replaced by the new ICommand.
|
118
|
+
#
|
119
|
+
# The Observer for the new ICommand is only created if this is the first time
|
120
|
+
# an ICommand has been registered for this notification name.
|
121
|
+
#
|
122
|
+
# @param notification_name [String] the name of the INotification
|
123
|
+
# @param factory [^<() -> ICommand>] the factory to produce an instance of the ICommand
|
124
|
+
def register_command(notification_name, &factory)
|
125
|
+
@command_mutex.synchronize do
|
126
|
+
if @command_map[notification_name].nil?
|
127
|
+
@view&.register_observer(notification_name, Observer.new(method(:execute_command), self))
|
128
|
+
end
|
129
|
+
@command_map[notification_name] = factory
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# If an ICommand has previously been registered to handle the given INotification, then it is executed.
|
134
|
+
#
|
135
|
+
# @param notification [INotification] the notification to handle
|
136
|
+
def execute_command(notification)
|
137
|
+
@command_mutex.synchronize do
|
138
|
+
# @type factory: [^() -> ICommand]?
|
139
|
+
factory = @command_map[notification.name]
|
140
|
+
return if factory.nil?
|
141
|
+
|
142
|
+
# @type var command: _ICommand
|
143
|
+
command = factory.call
|
144
|
+
command.initialize_notifier(@multiton_key)
|
145
|
+
command.execute(notification)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Check if a Command is registered for a given Notification.
|
150
|
+
#
|
151
|
+
# @param notification_name [String] the name of the Notification
|
152
|
+
# @return [Boolean] whether a Command is currently registered for the given notification_name
|
153
|
+
def has_command?(notification_name)
|
154
|
+
@command_mutex.synchronize do
|
155
|
+
@command_map.key?(notification_name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Remove a previously registered ICommand to INotification mapping.
|
160
|
+
#
|
161
|
+
# @param notification_name [String] the name of the INotification to remove the ICommand mapping for
|
162
|
+
def remove_command(notification_name)
|
163
|
+
@command_mutex.synchronize do
|
164
|
+
# @type var command: [^() -> ICommand]?
|
165
|
+
command = @command_map[notification_name]
|
166
|
+
|
167
|
+
# if the Command is registered...
|
168
|
+
if command
|
169
|
+
# remove the observer
|
170
|
+
@view&.remove_observer(notification_name, self)
|
171
|
+
# remove the command
|
172
|
+
@command_map.delete(notification_name)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/core/model.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# model.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 Multiton <code>IModel</code> implementation.
|
12
|
+
#
|
13
|
+
# In PureMVC, the <code>Model</code> class provides access to model objects (Proxies) by named lookup.
|
14
|
+
#
|
15
|
+
# The <code>Model</code> assumes these responsibilities:
|
16
|
+
#
|
17
|
+
# - Maintains a cache of <code>IProxy</code> instances.
|
18
|
+
# - Provides methods for registering, retrieving, and removing <code>IProxy</code> instances.
|
19
|
+
#
|
20
|
+
# Your application must register <code>IProxy</code> instances with the <code>Model</code>. Typically,
|
21
|
+
# an <code>ICommand</code> is used to create and register <code>IProxy</code> instances after the <code>Facade<code>
|
22
|
+
# has initialized the Core actors.
|
23
|
+
#
|
24
|
+
# @see Proxy
|
25
|
+
# @see IProxy
|
26
|
+
class Model
|
27
|
+
# Message Constants
|
28
|
+
MULTITON_MSG = 'Model instance for this Multiton key already constructed!'
|
29
|
+
private_constant :MULTITON_MSG
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# The Multiton IModel instanceMap.
|
33
|
+
# @return [Hash{String => IModel}]
|
34
|
+
def instance_map = (@instance_map ||= {})
|
35
|
+
|
36
|
+
# Mutex used to synchronize access to the instance map for thread safety.
|
37
|
+
# @return [Mutex]
|
38
|
+
def mutex = (@mutex ||= Mutex.new)
|
39
|
+
|
40
|
+
# <code>Model</code> Multiton Factory method.
|
41
|
+
#
|
42
|
+
# @param key [String] the unique key identifying the Multiton instance
|
43
|
+
# @param factory [^(String) -> IModel] the unique key passed to the factory block
|
44
|
+
# @return [IModel] the instance for this Multiton key
|
45
|
+
def get_instance(key, &factory)
|
46
|
+
mutex.synchronize do
|
47
|
+
instance_map[key] ||= factory.call(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove an IModel instance
|
52
|
+
#
|
53
|
+
# @param key [String] the multiton key of the IModel instance to remove
|
54
|
+
def remove_model(key)
|
55
|
+
mutex.synchronize do
|
56
|
+
instance_map.delete(key)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Constructor.
|
62
|
+
#
|
63
|
+
# This <code>IModel</code> implementation is a Multiton,
|
64
|
+
# so you should not call the constructor directly, but instead call the static
|
65
|
+
# Multiton Factory method <code>PureMVC::Model.get_instance(key) { |key| PureMVC::Model.new(key) }</code>.
|
66
|
+
#
|
67
|
+
# @param key [String]
|
68
|
+
# @raise [RuntimeError] Error if an instance for this Multiton key has already been constructed.
|
69
|
+
def initialize(key)
|
70
|
+
raise MULTITON_MSG if self.class.instance_map[key]
|
71
|
+
|
72
|
+
self.class.instance_map[key] = self
|
73
|
+
@multiton_key = key
|
74
|
+
@proxy_map = {}
|
75
|
+
@proxy_mutex = Mutex.new
|
76
|
+
initialize_model
|
77
|
+
end
|
78
|
+
|
79
|
+
# Initialize the <code>Model</code> instance.
|
80
|
+
#
|
81
|
+
# Called automatically by the constructor, this
|
82
|
+
# is your opportunity to initialize the Multiton
|
83
|
+
# instance in your subclass without overriding the
|
84
|
+
# constructor.
|
85
|
+
def initialize_model; end
|
86
|
+
|
87
|
+
# Register an <code>IProxy</code> with the <code>Model</code>.
|
88
|
+
#
|
89
|
+
# @param proxy [_IProxy] an <code>IProxy</code> to be held by the <code>Model</code>.
|
90
|
+
def register_proxy(proxy)
|
91
|
+
proxy.initialize_notifier(@multiton_key)
|
92
|
+
@proxy_mutex.synchronize do
|
93
|
+
@proxy_map[proxy.name] = proxy
|
94
|
+
end
|
95
|
+
proxy.on_register
|
96
|
+
end
|
97
|
+
|
98
|
+
# Retrieve an <code>IProxy</code> from the <code>Model</code>.
|
99
|
+
#
|
100
|
+
# @param proxy_name [String] the name of the proxy to retrieve.
|
101
|
+
# @return [_IProxy, nil] the <code>IProxy</code> instance previously registered with the <code>proxy_name</code>,
|
102
|
+
# or nil if none found.
|
103
|
+
def retrieve_proxy(proxy_name)
|
104
|
+
@proxy_mutex.synchronize do
|
105
|
+
@proxy_map[proxy_name]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check if a Proxy is registered.
|
110
|
+
#
|
111
|
+
# @param proxy_name [String] the name of the proxy to check.
|
112
|
+
# @return [Boolean] whether a Proxy is currently registered with the given <code>proxy_name</code>.
|
113
|
+
def has_proxy?(proxy_name)
|
114
|
+
@proxy_mutex.synchronize do
|
115
|
+
@proxy_map.key?(proxy_name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Remove an <code>IProxy</code> from the <code>Model</code>.
|
120
|
+
#
|
121
|
+
# @param proxy_name [String] name of the <code>IProxy</code> instance to be removed.
|
122
|
+
# @return [_IProxy, nil] the <code>IProxy</code> that was removed from the <code>Model</code>, or nil if none found.
|
123
|
+
def remove_proxy(proxy_name)
|
124
|
+
# @type var proxy: _IProxy?
|
125
|
+
proxy = nil
|
126
|
+
@proxy_mutex.synchronize do
|
127
|
+
proxy = @proxy_map.delete(proxy_name)
|
128
|
+
end
|
129
|
+
proxy&.on_remove
|
130
|
+
proxy
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/core/view.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
# view.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 Multiton <code>IView</code> implementation.
|
12
|
+
#
|
13
|
+
# In PureMVC, the <code>IView</code> class assumes these responsibilities:
|
14
|
+
#
|
15
|
+
# - Maintains a cache of <code>IMediator</code>instances.
|
16
|
+
# - Provides methods for registering, retrieving, and removing <code>IMediators</code>.
|
17
|
+
# - Notifies <code>IMediators</code>when they are registered or removed.
|
18
|
+
# - Manages the observer lists for each <code>INotification</code>in the application.
|
19
|
+
# - Provides a method for attaching <code>IObservers</code>to an <code>INotification</code>'s observer list.
|
20
|
+
# - Provides a method for broadcasting an <code>INotification</code>.
|
21
|
+
# - Notifies the <code>IObservers</code>of a given <code>INotification</code>when it is broadcast.
|
22
|
+
#
|
23
|
+
# @see Mediator
|
24
|
+
# @see Observer
|
25
|
+
# @see Notification
|
26
|
+
class View
|
27
|
+
MULTITON_MSG = 'View instance for this Multiton key already constructed!'
|
28
|
+
private_constant :MULTITON_MSG
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# The Multiton IModel instanceMap.
|
32
|
+
# @return [Hash{String => IView}]
|
33
|
+
def instance_map = (@instance_map ||= {})
|
34
|
+
|
35
|
+
# Mutex used to synchronize access to the instance map for thread safety.
|
36
|
+
# @return [Mutex]
|
37
|
+
def mutex = (@mutex ||= Mutex.new)
|
38
|
+
|
39
|
+
# View Multiton Factory method.
|
40
|
+
#
|
41
|
+
# @param key [String] the unique key identifying the Multiton instance
|
42
|
+
# @param factory [^(String) -> _IView] the unique key passed to the factory block
|
43
|
+
# @return [_IView] the Multiton instance of <code>View</code>
|
44
|
+
def get_instance(key, &factory)
|
45
|
+
mutex.synchronize do
|
46
|
+
instance_map[key] ||= factory.call(key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Remove an <code>IView</code> instance.
|
51
|
+
#
|
52
|
+
# @param key [String] the key of the <code>IView</code> instance to remove
|
53
|
+
def remove_view(key)
|
54
|
+
mutex.synchronize do
|
55
|
+
instance_map.delete(key)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Constructor.
|
61
|
+
#
|
62
|
+
# This <code>IView</code> implementation is a Multiton,
|
63
|
+
# so you should not call the constructor directly. Instead, call the static
|
64
|
+
# Multiton factory method <code>View.get_instance(multiton_key) { |key| View.new(key) }</code>.
|
65
|
+
#
|
66
|
+
# @param key [String]
|
67
|
+
# @raise [RuntimeError] if an instance for this Multiton key has already been constructed.
|
68
|
+
def initialize(key)
|
69
|
+
raise MULTITON_MSG if self.class.instance_map[key]
|
70
|
+
|
71
|
+
self.class.instance_map[key] = self
|
72
|
+
# The Multiton Key for this Core
|
73
|
+
# @type var multiton_key: String
|
74
|
+
@multiton_key = key
|
75
|
+
# Mapping of Notification names to Observer lists
|
76
|
+
# @type var observer_map: Hash[String, Array[_IObserver]]
|
77
|
+
@observer_map = {}
|
78
|
+
# Mutex used to synchronize access to the observer_map
|
79
|
+
# @type var observer_mutex: Mutex
|
80
|
+
@observer_mutex = Mutex.new
|
81
|
+
# Mapping of Mediator names to Mediator instances
|
82
|
+
# @type var mediator_map: Hash[String, _IMediator]
|
83
|
+
@mediator_map = {}
|
84
|
+
# Mutex used to synchronize access to the mediator_map
|
85
|
+
# @type var mediator_mutex: Mutex
|
86
|
+
@mediator_mutex = Mutex.new
|
87
|
+
initialize_view
|
88
|
+
end
|
89
|
+
|
90
|
+
# Initialize the Multiton <code>View</code> instance.
|
91
|
+
#
|
92
|
+
# Called automatically by the constructor, this
|
93
|
+
# is your opportunity to initialize the Multiton
|
94
|
+
# instance in your subclass without overriding the
|
95
|
+
# constructor.
|
96
|
+
def initialize_view; end
|
97
|
+
|
98
|
+
# Register an <code>IObserver</code> to be notified
|
99
|
+
# of <code>INotifications</code> with a given name.
|
100
|
+
#
|
101
|
+
# @param notification_name [String] the name of the <code>INotifications</code> to notify <code>IObserver</code> of
|
102
|
+
# @param observer [IObserver] the <code>IObserver</code> to register
|
103
|
+
def register_observer(notification_name, observer)
|
104
|
+
@observer_mutex.synchronize do
|
105
|
+
observers = (@observer_map[notification_name] ||= [])
|
106
|
+
observers << observer
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Notify the <code>IObservers</code> for a particular <code>INotification</code>.
|
111
|
+
#
|
112
|
+
# All previously attached <code>IObservers</code> for this <code>INotification</code>'s
|
113
|
+
# list are notified and are passed a reference to the <code>INotification</code> in
|
114
|
+
# the order in which they were registered.
|
115
|
+
#
|
116
|
+
# @param notification [INotification] the <code>INotification</code> to notify <code>IObservers</code> of.
|
117
|
+
def notify_observers(notification)
|
118
|
+
# @type var observers: Array[_IObserver]?
|
119
|
+
observers = nil
|
120
|
+
@observer_mutex.synchronize do
|
121
|
+
# Get a reference to the observers list for this notification name
|
122
|
+
# Iteration safe, copy observers from reference array to working array,
|
123
|
+
# since the reference array may change during the notification loop
|
124
|
+
observers = @observer_map[notification.name].dup
|
125
|
+
end
|
126
|
+
# Notify Observers from the working array
|
127
|
+
observers&.each { |observer| observer.notify_observer(notification) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove the observer for a given notifyContext from an observer list for a given Notification name.
|
131
|
+
#
|
132
|
+
# @param notification_name [String] which observer list to remove from
|
133
|
+
# @param notify_context [Object] remove the observer with this object as its notifyContext
|
134
|
+
def remove_observer(notification_name, notify_context)
|
135
|
+
@observer_mutex.synchronize do
|
136
|
+
# the observer list for the notification under inspection
|
137
|
+
# @type var observers: Array[_IObserver]?
|
138
|
+
observers = @observer_map[notification_name]
|
139
|
+
# find and remove the sole Observer for the given notify_context
|
140
|
+
# there can only be one Observer for a given notify_context
|
141
|
+
# in any given Observer list, so remove it
|
142
|
+
observers&.reject! { |observer| observer.compare_notify_context?(notify_context) }
|
143
|
+
# Also, when a Notification's Observer list length falls to
|
144
|
+
# zero, delete the notification key from the observer map
|
145
|
+
@observer_map.delete(notification_name) if observers&.empty?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Register an <code>IMediator</code> instance with the <code>View</code>.
|
150
|
+
#
|
151
|
+
# Registers the <code>IMediator</code> so that it can be retrieved by name,
|
152
|
+
# and further interrogates the <code>IMediator</code> for its
|
153
|
+
# <code>INotification</code> interests.
|
154
|
+
#
|
155
|
+
# If the <code>IMediator</code> returns any <code>INotification</code>
|
156
|
+
# names to be notified about, an <code>Observer</code> is created encapsulating
|
157
|
+
# the <code>IMediator</code> instance's <code>handleNotification</code> method
|
158
|
+
# and registering it as an <code>Observer</code> for all <code>INotifications</code> the
|
159
|
+
# <code>IMediator</code> is interested in.
|
160
|
+
#
|
161
|
+
# @param mediator [IMediator] a reference to the <code>IMediator</code> instance
|
162
|
+
def register_mediator(mediator)
|
163
|
+
@mediator_mutex.synchronize do
|
164
|
+
return if @mediator_map.key?(mediator.name)
|
165
|
+
|
166
|
+
@mediator_map[mediator.name] = mediator
|
167
|
+
end
|
168
|
+
|
169
|
+
mediator.initialize_notifier(@multiton_key)
|
170
|
+
|
171
|
+
# Create Observer referencing this mediator's handleNotification method
|
172
|
+
# @type observer [IObserver]
|
173
|
+
observer = Observer.new(mediator.method(:handle_notification), mediator)
|
174
|
+
|
175
|
+
# Get Notification interests, if any.
|
176
|
+
# @type interests [Array<String>]
|
177
|
+
interests = mediator.list_notification_interests
|
178
|
+
# Register Mediator as Observer for its list of Notification interests
|
179
|
+
interests.each { |interest| register_observer(interest, observer) }
|
180
|
+
|
181
|
+
# alert the mediator that it has been registered
|
182
|
+
mediator.on_register
|
183
|
+
end
|
184
|
+
|
185
|
+
# Retrieve an <code>IMediator</code> from the <code>View</code>.
|
186
|
+
#
|
187
|
+
# @param mediator_name [String] the name of the <code>IMediator</code> instance to retrieve.
|
188
|
+
# @return [IMediator, nil] <code>IMediator</code> previously registered with the given <code>mediatorName</code>.
|
189
|
+
def retrieve_mediator(mediator_name)
|
190
|
+
@mediator_mutex.synchronize do
|
191
|
+
@mediator_map[mediator_name]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Check if a Mediator is registered or not.
|
196
|
+
#
|
197
|
+
# @param mediator_name [String] the name of the mediator to check.
|
198
|
+
# @return [Boolean] whether a Mediator is registered with the given <code>mediatorName</code>.
|
199
|
+
def has_mediator?(mediator_name)
|
200
|
+
@mediator_mutex.synchronize do
|
201
|
+
@mediator_map.key?(mediator_name)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Remove an <code>IMediator</code> from the <code>View</code>.
|
206
|
+
#
|
207
|
+
# @param mediator_name [String] name of the <code>IMediator</code> instance to be removed.
|
208
|
+
# @return [IMediator, nil] <code>IMediator</code> that was removed from the <code>View</code>, or nil if none found.
|
209
|
+
def remove_mediator(mediator_name)
|
210
|
+
# @type var mediator: _IMediator?
|
211
|
+
mediator = nil
|
212
|
+
@mediator_mutex.synchronize do
|
213
|
+
# retrieve the named mediator and delete from the mediator map
|
214
|
+
mediator = @mediator_map.delete(mediator_name)
|
215
|
+
end
|
216
|
+
return unless mediator
|
217
|
+
|
218
|
+
# for every notification this mediator is interested in...
|
219
|
+
# @type var interests: Array[String]
|
220
|
+
interests = mediator.list_notification_interests
|
221
|
+
# remove the observer linking the mediator
|
222
|
+
# to the notification interest
|
223
|
+
interests.each { |interest| remove_observer(interest, mediator) }
|
224
|
+
|
225
|
+
# alert the mediator that it has been removed
|
226
|
+
mediator.on_remove
|
227
|
+
mediator
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|