mediate 0.1.0 → 0.1.3
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +56 -8
- data/lib/mediate/mediator.rb +20 -19
- data/lib/mediate/request.rb +13 -18
- data/lib/mediate/version.rb +1 -1
- data/lib/mediate.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bf88906a3d562b832c1581d5775d2f6ae2b5a7d93b59166882edec77217dde5
|
|
4
|
+
data.tar.gz: 0c49d3981d01e37c29e3276672e2527a2fb61a67d9f17d6d51a48a0247ebffcb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efe642b92ac4763623c79af4bdc79b330544eb320e07075061ba5cec489b98cc5af62f07de4baaf34435c13ebbeedddd91611b6b137e7811bfe79d091b84d7c4
|
|
7
|
+
data.tar.gz: 34e033244ffcd8438060c5d6c7e05046a572b5583047b75f3438323f10e9c774d5a140066a6d7911e8da43fc01ebb52354fee7d60a9f5fbf951517aa58237d79
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Mediate
|
|
2
2
|
|
|
3
|
+
[](https://github.com/rferg/mediate/actions/workflows/ci.yml) [](https://badge.fury.io/rb/mediate)
|
|
4
|
+
|
|
3
5
|
A simple mediator implementation for Ruby inspired by [Mediatr](https://github.com/jbogard/MediatR).
|
|
4
6
|
|
|
5
7
|
Decouple application components by sending a request through the mediator and receiving a response from a handler, instead of directly calling methods on imported classes.
|
|
@@ -17,6 +19,8 @@ Supports request/response, notifications (i.e., events), pre- and post-request h
|
|
|
17
19
|
- [Testing](#testing)
|
|
18
20
|
- [Testing implicit request handlers](#testing-implicit-request-handlers)
|
|
19
21
|
- [Using with Rails](#using-with-rails)
|
|
22
|
+
- [Nest request handler definitions within request classes](#nest-request-handler-definitions-within-request-classes)
|
|
23
|
+
- [Configure Rails to eager load other handlers in non-production environments](#configure-rails-to-eager-load-other-handlers-in-non-production-environments)
|
|
20
24
|
- [Development](#development)
|
|
21
25
|
- [Contributing](#contributing)
|
|
22
26
|
- [License](#license)
|
|
@@ -96,7 +100,7 @@ Note that only one handler can be registered for a particular request class; att
|
|
|
96
100
|
|
|
97
101
|
#### Implicit handler declaration
|
|
98
102
|
|
|
99
|
-
For simple handlers, you can skip the explicit `RequestHandler` declaration above and instead pass a
|
|
103
|
+
For simple handlers, you can skip the explicit `RequestHandler` declaration above and instead pass a lambda to `Request.handle_with`.
|
|
100
104
|
|
|
101
105
|
```ruby
|
|
102
106
|
class Ping < Mediate::Request
|
|
@@ -107,14 +111,14 @@ class Ping < Mediate::Request
|
|
|
107
111
|
super()
|
|
108
112
|
end
|
|
109
113
|
# This will have the same behavior as the PingHandler declaration above.
|
|
110
|
-
handle_with {
|
|
114
|
+
handle_with ->(request) { "Received: #{request.message}" }
|
|
111
115
|
end
|
|
112
116
|
|
|
113
117
|
response = Mediate.dispatch(Ping.new('hello'))
|
|
114
118
|
puts response # 'Received: hello'
|
|
115
119
|
```
|
|
116
120
|
|
|
117
|
-
Behind the scenes, this defines a `Ping::Handler` class that calls the given
|
|
121
|
+
Behind the scenes, this defines a `Ping::Handler` class that calls the given lambda in its `handle` method. For testing purposes, you can get an instance of this handler class by calling `Mediate::Request.create_implicit_handler` (see [Testing implicit request handlers](#testing-implicit-request-handlers)).
|
|
118
122
|
|
|
119
123
|
#### Request polymorphism
|
|
120
124
|
|
|
@@ -129,7 +133,7 @@ Unless we registered a handler for `SubPing` explicitly.
|
|
|
129
133
|
|
|
130
134
|
```ruby
|
|
131
135
|
class SubPing < Ping
|
|
132
|
-
handle_with {
|
|
136
|
+
handle_with ->(request) { "Received from SubPing: #{request.message}" }
|
|
133
137
|
end
|
|
134
138
|
puts Mediate.dispatch(SubPing.new('howdy')) # 'Received from SubPing: howdy'
|
|
135
139
|
```
|
|
@@ -252,13 +256,13 @@ Special consideration is only required when testing paths that invoke methods on
|
|
|
252
256
|
|
|
253
257
|
#### Testing implicit request handlers
|
|
254
258
|
|
|
255
|
-
How can you test a request handler defined using `handle_with` and a
|
|
259
|
+
How can you test a request handler defined using `handle_with` and a lambda like the following?
|
|
256
260
|
|
|
257
261
|
```ruby
|
|
258
262
|
class ExampleRequest < Mediate::Request
|
|
259
|
-
handle_with
|
|
263
|
+
handle_with lambda { |request|
|
|
260
264
|
# ....
|
|
261
|
-
|
|
265
|
+
}
|
|
262
266
|
end
|
|
263
267
|
```
|
|
264
268
|
|
|
@@ -276,7 +280,51 @@ end
|
|
|
276
280
|
|
|
277
281
|
### Using with Rails
|
|
278
282
|
|
|
279
|
-
|
|
283
|
+
Handler registrations occur within methods called from class definitions. In non-production environments, by default, Rails [lazy loads](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html) constants. Therefore, if handlers are not explicitly referenced, which is typical, their class definitions will not be loaded and they will not be registered as handlers on the mediator.
|
|
284
|
+
|
|
285
|
+
There are two things that need to be done to work around this behavior:
|
|
286
|
+
|
|
287
|
+
#### Nest request handler definitions within request classes
|
|
288
|
+
|
|
289
|
+
Since requests will be explicitly referenced in, say, controllers, we can force their handler constants to load with them by nesting those definitions within the request definitions. For example:
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
class MyRequest < Mediate::Request
|
|
293
|
+
# ...
|
|
294
|
+
class MyRequestHandler < Mediate::RequestHandler
|
|
295
|
+
handles MyRequest
|
|
296
|
+
|
|
297
|
+
def handle(request)
|
|
298
|
+
# ...
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
This has the added benefit of colocating a handler with its request, making it easy to find. This is therefore the recommended way to declare requests and their handlers. [Implicit handler declarations](#implicit-handler-declaration) do this automatically.
|
|
305
|
+
|
|
306
|
+
#### Configure Rails to eager load other handlers in non-production environments
|
|
307
|
+
|
|
308
|
+
For other handlers (pre- and post-request behaviors, error handlers, notification handlers), it is typically either not possible or not practical to nest their declarations within the class definitions they handle. You will have to add configuration to eager load these in environments where global eager loading is turned off. This can be accomplished by adding their file paths to `config.eager_load_paths` in the relevant environment files, like so:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
# config/environments/development.rb
|
|
312
|
+
|
|
313
|
+
Rails.application.configure do
|
|
314
|
+
# ...
|
|
315
|
+
[
|
|
316
|
+
'app/use_cases/common/**/*.rb',
|
|
317
|
+
'app/use_cases/**/event_handlers/**/*.rb'
|
|
318
|
+
].each do |path|
|
|
319
|
+
config.eager_load_paths += Dir[path]
|
|
320
|
+
ActiveSupport::Reloader.to_prepare do
|
|
321
|
+
Dir[path].each { |f| require_dependency("#{Dir.pwd}/#{f}") }
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
(In the above, we're assuming, for example, that we have pre- and post-request behaviors and error handlers in `app/use_cases/common/` and notification handlers in `app/use_cases/**/event_handlers/**/`.)
|
|
280
328
|
|
|
281
329
|
## Development
|
|
282
330
|
|
data/lib/mediate/mediator.rb
CHANGED
|
@@ -42,16 +42,16 @@ module Mediate
|
|
|
42
42
|
def dispatch(request)
|
|
43
43
|
raise ArgumentError, "request cannot be nil" if request.nil?
|
|
44
44
|
|
|
45
|
-
request_handler = resolve_handler(
|
|
45
|
+
request_handler = resolve_handler(request_handlers, request.class, REQUEST_BASE)
|
|
46
46
|
raise Errors::NoHandlerError, request.class if request_handler == NullHandler
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
run_request_pipeline(request,
|
|
48
|
+
prerequest = collect_by_inheritance(prerequest_behaviors, request.class, REQUEST_BASE)
|
|
49
|
+
postrequest = collect_by_inheritance(postrequest_behaviors, request.class, REQUEST_BASE)
|
|
50
|
+
run_request_pipeline(request, prerequest, request_handler, postrequest)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
#
|
|
54
|
-
# Sends a notification to all
|
|
54
|
+
# Sends a notification to all registered handlers for the given notification's type.
|
|
55
55
|
#
|
|
56
56
|
# @param [Mediate::Notification] notification
|
|
57
57
|
#
|
|
@@ -60,7 +60,7 @@ module Mediate
|
|
|
60
60
|
def publish(notification)
|
|
61
61
|
raise ArgumentError, "notification cannot be nil" if notification.nil?
|
|
62
62
|
|
|
63
|
-
handler_classes = collect_by_inheritance(
|
|
63
|
+
handler_classes = collect_by_inheritance(notification_handlers, notification.class, NOTIF_BASE)
|
|
64
64
|
handler_classes.each do |handler_class|
|
|
65
65
|
handler_class.new.handle(notification)
|
|
66
66
|
rescue StandardError => e
|
|
@@ -73,26 +73,31 @@ module Mediate
|
|
|
73
73
|
def register_request_handler(handler_class, request_class)
|
|
74
74
|
validate_base_class(handler_class, Mediate::RequestHandler)
|
|
75
75
|
validate_base_class(request_class, REQUEST_BASE)
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
request_handlers.compute(request_class) do |old_value|
|
|
77
|
+
unless old_value.nil? || old_value == handler_class
|
|
78
|
+
raise Errors::RequestHandlerAlreadyExistsError.new(request_class, old_value, handler_class)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
handler_class
|
|
82
|
+
end
|
|
78
83
|
end
|
|
79
84
|
|
|
80
85
|
def register_notification_handler(handler_class, notif_class)
|
|
81
86
|
validate_base_class(handler_class, Mediate::NotificationHandler)
|
|
82
87
|
validate_base_class(notif_class, NOTIF_BASE, allow_base: true)
|
|
83
|
-
append_to_hash_value(
|
|
88
|
+
append_to_hash_value(notification_handlers, notif_class, handler_class)
|
|
84
89
|
end
|
|
85
90
|
|
|
86
91
|
def register_prerequest_behavior(behavior_class, request_class)
|
|
87
92
|
validate_base_class(behavior_class, Mediate::PrerequestBehavior)
|
|
88
93
|
validate_base_class(request_class, REQUEST_BASE, allow_base: true)
|
|
89
|
-
append_to_hash_value(
|
|
94
|
+
append_to_hash_value(prerequest_behaviors, request_class, behavior_class)
|
|
90
95
|
end
|
|
91
96
|
|
|
92
97
|
def register_postrequest_behavior(behavior_class, request_class)
|
|
93
98
|
validate_base_class(behavior_class, Mediate::PostrequestBehavior)
|
|
94
99
|
validate_base_class(request_class, REQUEST_BASE, allow_base: true)
|
|
95
|
-
append_to_hash_value(
|
|
100
|
+
append_to_hash_value(postrequest_behaviors, request_class, behavior_class)
|
|
96
101
|
end
|
|
97
102
|
|
|
98
103
|
def register_error_handler(handler_class, exception_class, dispatch_class)
|
|
@@ -119,18 +124,14 @@ module Mediate
|
|
|
119
124
|
|
|
120
125
|
private
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return if registered.nil? || registered == new_handler
|
|
125
|
-
|
|
126
|
-
raise Errors::RequestHandlerAlreadyExistsError.new(request_class, registered, new_handler)
|
|
127
|
-
end
|
|
127
|
+
attr_reader :exception_handlers, :postrequest_behaviors, :prerequest_behaviors, :notification_handlers,
|
|
128
|
+
:request_handlers
|
|
128
129
|
|
|
129
130
|
def register_error_handler_for_dispatch(handler_class, exception_class, dispatch_class, dispatch_base_class)
|
|
130
131
|
validate_base_class(handler_class, Mediate::ErrorHandler)
|
|
131
132
|
validate_base_class(exception_class, StandardError, allow_base: true)
|
|
132
133
|
validate_base_class(dispatch_class, dispatch_base_class, allow_base: true)
|
|
133
|
-
map =
|
|
134
|
+
map = exception_handlers.fetch_or_store(exception_class, Concurrent::Map.new)
|
|
134
135
|
append_to_hash_value(map, dispatch_class, handler_class)
|
|
135
136
|
end
|
|
136
137
|
|
|
@@ -157,7 +158,7 @@ module Mediate
|
|
|
157
158
|
end
|
|
158
159
|
|
|
159
160
|
def handle_exception(dispatched, exception, dispatch_base_class)
|
|
160
|
-
exception_to_dispatched_maps = collect_by_inheritance(
|
|
161
|
+
exception_to_dispatched_maps = collect_by_inheritance(exception_handlers, exception.class, StandardError)
|
|
161
162
|
handler_classes = exception_to_dispatched_maps.reduce(Concurrent::Set.new) do |memo, curr|
|
|
162
163
|
collect_by_inheritance(curr, dispatched.class, dispatch_base_class, memo)
|
|
163
164
|
end
|
data/lib/mediate/request.rb
CHANGED
|
@@ -9,27 +9,25 @@ module Mediate
|
|
|
9
9
|
IMPLICIT_HANDLER_CLASS_NAME = "Handler"
|
|
10
10
|
|
|
11
11
|
#
|
|
12
|
-
# Registers a handler for this Request type using the given
|
|
12
|
+
# Registers a handler for this Request type using the given lambda as the handle method.
|
|
13
13
|
#
|
|
14
|
+
# @param [Lambda] lmbda the block that will handle the request
|
|
14
15
|
# @param [Mediate::Mediator] mediator the instance to register the handler on
|
|
15
|
-
# @param [Proc] &proc the block that will handle the request
|
|
16
16
|
#
|
|
17
|
-
# @raises [ArgumentError] if no
|
|
17
|
+
# @raises [ArgumentError] if no lambda is given
|
|
18
|
+
# @raises [RequestHandlerAlreadyExistsError] if handler already defined for this class
|
|
18
19
|
#
|
|
19
|
-
# @example When a request of this type is dispatched, the handle_with
|
|
20
|
+
# @example When a request of this type is dispatched, the handle_with lambda will run
|
|
20
21
|
# class MyRequest < Mediate::Request
|
|
21
|
-
# handle_with do |request|
|
|
22
|
+
# handle_with lambda do |request|
|
|
22
23
|
# ## do something with request...
|
|
23
24
|
# end
|
|
24
25
|
# end
|
|
25
|
-
def self.handle_with(mediator = Mediate.mediator
|
|
26
|
-
raise ArgumentError, "expected
|
|
26
|
+
def self.handle_with(lmbda, mediator = Mediate.mediator)
|
|
27
|
+
raise ArgumentError, "expected lambda to be passed to #handle_with." if lmbda.nil?
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
handler_class = define_handler(proc)
|
|
29
|
+
handler_class = define_handler(lmbda)
|
|
30
|
+
undefine_implicit_handler # remove any previous definition (this will do nothing if it doesn't exist)
|
|
33
31
|
const_set(IMPLICIT_HANDLER_CLASS_NAME, handler_class)
|
|
34
32
|
mediator.register_request_handler(handler_class, self)
|
|
35
33
|
end
|
|
@@ -57,17 +55,14 @@ module Mediate
|
|
|
57
55
|
remove_const(IMPLICIT_HANDLER_CLASS_NAME)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
|
-
def self.define_handler(
|
|
58
|
+
def self.define_handler(lmbda)
|
|
61
59
|
Class.new(RequestHandler) do
|
|
62
|
-
|
|
63
|
-
def handle(request)
|
|
64
|
-
@@handle_proc.call(request)
|
|
65
|
-
end
|
|
60
|
+
define_method(:handle, lmbda)
|
|
66
61
|
end
|
|
67
62
|
end
|
|
68
63
|
|
|
69
64
|
def self.implicit_handler_defined?
|
|
70
|
-
const_defined?(IMPLICIT_HANDLER_CLASS_NAME)
|
|
65
|
+
const_defined?(IMPLICIT_HANDLER_CLASS_NAME, false) # do not check ancestors
|
|
71
66
|
end
|
|
72
67
|
|
|
73
68
|
private_constant :IMPLICIT_HANDLER_CLASS_NAME
|
data/lib/mediate/version.rb
CHANGED
data/lib/mediate.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Mediate
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
#
|
|
36
|
-
# Sends a notification to all
|
|
36
|
+
# Sends a notification to all registered handlers for the given notification's type.
|
|
37
37
|
#
|
|
38
38
|
# @param [Mediate::Notification] notification
|
|
39
39
|
#
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mediate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Ferguson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-06-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|