mediate 0.1.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d836d3d2c7c76a49fe26644d4fb93ce98f84ae6b77cb1a47ab0db43c3ccf3d2
4
- data.tar.gz: 87b88af20dd82d6cadb44261d47864fb032f5c27648f1e50092c6ad2572f6495
3
+ metadata.gz: 6bf88906a3d562b832c1581d5775d2f6ae2b5a7d93b59166882edec77217dde5
4
+ data.tar.gz: 0c49d3981d01e37c29e3276672e2527a2fb61a67d9f17d6d51a48a0247ebffcb
5
5
  SHA512:
6
- metadata.gz: a8c012bc18b9939dcea6264760d45516359569fa865e725e488cb00ead4a22ab977ce1c6f6561ed256031b0c06824b998cbf090fe09fc2654a4273f5e6310584
7
- data.tar.gz: 769e0b47ce6458170e422aefb56ff89bfde68adef9fdfe1b5ca65ae47ce2f138aa2b0407664c1910942fc81b5eac6af112d34d0a83513810c43e3b8b0f372eb3
6
+ metadata.gz: efe642b92ac4763623c79af4bdc79b330544eb320e07075061ba5cec489b98cc5af62f07de4baaf34435c13ebbeedddd91611b6b137e7811bfe79d091b84d7c4
7
+ data.tar.gz: 34e033244ffcd8438060c5d6c7e05046a572b5583047b75f3438323f10e9c774d5a140066a6d7911e8da43fc01ebb52354fee7d60a9f5fbf951517aa58237d79
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mediate (0.1.2)
4
+ mediate (0.1.3)
5
5
  concurrent-ruby (~> 1.1)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Mediate
2
2
 
3
+ [![CI](https://github.com/rferg/mediate/actions/workflows/ci.yml/badge.svg)](https://github.com/rferg/mediate/actions/workflows/ci.yml) [![Gem Version](https://badge.fury.io/rb/mediate.svg)](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)
@@ -256,9 +260,9 @@ How can you test a request handler defined using `handle_with` and a lambda like
256
260
 
257
261
  ```ruby
258
262
  class ExampleRequest < Mediate::Request
259
- handle_with lambda do |request|
263
+ handle_with lambda { |request|
260
264
  # ....
261
- end
265
+ }
262
266
  end
263
267
  ```
264
268
 
@@ -276,7 +280,51 @@ end
276
280
 
277
281
  ### Using with Rails
278
282
 
279
- TODO
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
 
@@ -42,12 +42,12 @@ 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(@request_handlers, request.class, REQUEST_BASE)
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
- prerequest_handlers = collect_by_inheritance(@prerequest_behaviors, request.class, REQUEST_BASE)
49
- postrequest_handlers = collect_by_inheritance(@postrequest_behaviors, request.class, REQUEST_BASE)
50
- run_request_pipeline(request, prerequest_handlers, request_handler, postrequest_handlers)
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
  #
@@ -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(@notification_handlers, notification.class, NOTIF_BASE)
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
- raise_if_request_handler_exists(request_class, handler_class)
77
- @request_handlers[request_class] = handler_class
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(@notification_handlers, notif_class, handler_class)
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(@prerequest_behaviors, request_class, behavior_class)
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(@postrequest_behaviors, request_class, behavior_class)
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
- def raise_if_request_handler_exists(request_class, new_handler)
123
- registered = @request_handlers.fetch(request_class, nil)
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 = @exception_handlers.fetch_or_store(exception_class, Concurrent::Map.new)
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(@exception_handlers, exception.class, StandardError)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mediate
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
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.2
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: 2022-08-08 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby