mediate 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 06e45660926271c9435a4c2dd1634974b3873091f12160b907833cab8c9b7abf
4
+ data.tar.gz: 8b1fe7523830f57f8e2499bf8c3575ffc0eef7716478a554f683947a8958dd95
5
+ SHA512:
6
+ metadata.gz: 6f29b77858c6361cbdc54ccc79656d470dd2feb274fe341746fa66d9fc33e34a7c1906bc8df859dfbb0dbf68fa73fc5f1b7819a2bb889565c91388abfbd8d090
7
+ data.tar.gz: 20c32a19443169eb74e41524b8ae9a342a023a2f54a306d182c3419988dbc5479a739c5866012d29a83e1cb1aeace714f6677efaab7c6368d90082fa0655c879
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,140 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ Gemspec/DeprecatedAttributeAssignment: # new in 1.30
4
+ Enabled: true
5
+ Gemspec/RequireMFA: # new in 1.23
6
+ Enabled: true
7
+ Layout/LineContinuationLeadingSpace: # new in 1.31
8
+ Enabled: true
9
+ Layout/LineContinuationSpacing: # new in 1.31
10
+ Enabled: true
11
+ Layout/LineEndStringConcatenationIndentation: # new in 1.18
12
+ Enabled: true
13
+ Layout/LineLength:
14
+ Max: 120
15
+ Layout/SpaceBeforeBrackets: # new in 1.7
16
+ Enabled: true
17
+ Lint/AmbiguousAssignment: # new in 1.7
18
+ Enabled: true
19
+ Lint/AmbiguousOperatorPrecedence: # new in 1.21
20
+ Enabled: true
21
+ Lint/AmbiguousRange: # new in 1.19
22
+ Enabled: true
23
+ Lint/ConstantOverwrittenInRescue: # new in 1.31
24
+ Enabled: true
25
+ Lint/DeprecatedConstants: # new in 1.8
26
+ Enabled: true
27
+ Lint/DuplicateBranch: # new in 1.3
28
+ Enabled: true
29
+ Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
30
+ Enabled: true
31
+ Lint/EmptyBlock: # new in 1.1
32
+ Enabled: true
33
+ Lint/EmptyClass: # new in 1.3
34
+ Enabled: false
35
+ Lint/EmptyInPattern: # new in 1.16
36
+ Enabled: true
37
+ Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
38
+ Enabled: true
39
+ Lint/LambdaWithoutLiteralBlock: # new in 1.8
40
+ Enabled: true
41
+ Lint/NoReturnInBeginEndBlocks: # new in 1.2
42
+ Enabled: true
43
+ Lint/NonAtomicFileOperation: # new in 1.31
44
+ Enabled: true
45
+ Lint/NumberedParameterAssignment: # new in 1.9
46
+ Enabled: true
47
+ Lint/OrAssignmentToConstant: # new in 1.9
48
+ Enabled: true
49
+ Lint/RedundantDirGlobSort: # new in 1.8
50
+ Enabled: true
51
+ Lint/RefinementImportMethods: # new in 1.27
52
+ Enabled: true
53
+ Lint/RequireRelativeSelfPath: # new in 1.22
54
+ Enabled: true
55
+ Lint/SymbolConversion: # new in 1.9
56
+ Enabled: true
57
+ Lint/ToEnumArguments: # new in 1.1
58
+ Enabled: true
59
+ Lint/TripleQuotes: # new in 1.9
60
+ Enabled: true
61
+ Lint/UnexpectedBlockArity: # new in 1.5
62
+ Enabled: true
63
+ Lint/UnmodifiedReduceAccumulator: # new in 1.1
64
+ Enabled: true
65
+ Lint/UselessRuby2Keywords: # new in 1.23
66
+ Enabled: true
67
+ Metrics/BlockLength:
68
+ IgnoredMethods: ['describe', 'context']
69
+ Metrics/ClassLength:
70
+ Max: 150
71
+ Naming/BlockForwarding: # new in 1.24
72
+ Enabled: true
73
+ Security/CompoundHash: # new in 1.28
74
+ Enabled: true
75
+ Security/IoMethods: # new in 1.22
76
+ Enabled: true
77
+ Style/ArgumentsForwarding: # new in 1.1
78
+ Enabled: true
79
+ Style/CollectionCompact: # new in 1.2
80
+ Enabled: true
81
+ Style/DocumentDynamicEvalDefinition: # new in 1.1
82
+ Enabled: true
83
+ Style/EndlessMethod: # new in 1.8
84
+ Enabled: true
85
+ Style/EnvHome: # new in 1.29
86
+ Enabled: true
87
+ Style/FetchEnvVar: # new in 1.28
88
+ Enabled: true
89
+ Style/FileRead: # new in 1.24
90
+ Enabled: true
91
+ Style/FileWrite: # new in 1.24
92
+ Enabled: true
93
+ Style/HashConversion: # new in 1.10
94
+ Enabled: true
95
+ Style/HashExcept: # new in 1.7
96
+ Enabled: true
97
+ Style/IfWithBooleanLiteralBranches: # new in 1.9
98
+ Enabled: true
99
+ Style/InPatternThen: # new in 1.16
100
+ Enabled: true
101
+ Style/MapCompactWithConditionalBlock: # new in 1.30
102
+ Enabled: true
103
+ Style/MapToHash: # new in 1.24
104
+ Enabled: true
105
+ Style/MultilineInPatternThen: # new in 1.16
106
+ Enabled: true
107
+ Style/NegatedIfElseCondition: # new in 1.2
108
+ Enabled: true
109
+ Style/NestedFileDirname: # new in 1.26
110
+ Enabled: true
111
+ Style/NilLambda: # new in 1.3
112
+ Enabled: true
113
+ Style/NumberedParameters: # new in 1.22
114
+ Enabled: true
115
+ Style/NumberedParametersLimit: # new in 1.22
116
+ Enabled: true
117
+ Style/ObjectThen: # new in 1.28
118
+ Enabled: true
119
+ Style/OpenStructUse: # new in 1.23
120
+ Enabled: true
121
+ Style/QuotedSymbols: # new in 1.16
122
+ Enabled: true
123
+ Style/RedundantArgument: # new in 1.4
124
+ Enabled: true
125
+ Style/RedundantInitialize: # new in 1.27
126
+ Enabled: true
127
+ Style/RedundantSelfAssignmentBranch: # new in 1.19
128
+ Enabled: true
129
+ Style/SelectByRegexp: # new in 1.22
130
+ Enabled: true
131
+ Style/StringChars: # new in 1.12
132
+ Enabled: true
133
+ Style/StringLiterals:
134
+ Enabled: true
135
+ EnforcedStyle: double_quotes
136
+ Style/StringLiteralsInInterpolation:
137
+ Enabled: true
138
+ EnforcedStyle: double_quotes
139
+ Style/SwapValues: # new in 1.1
140
+ Enabled: true
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": []
3
+ }
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in mediate.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.21"
13
+
14
+ gem "yard", "~> 0.9.28"
15
+
16
+ gem "concurrent-ruby", "~> 1.1", require: "concurrent"
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mediate (0.1.0)
5
+ concurrent-ruby (~> 1.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ concurrent-ruby (1.1.10)
12
+ diff-lcs (1.5.0)
13
+ json (2.6.2)
14
+ parallel (1.22.1)
15
+ parser (3.1.2.0)
16
+ ast (~> 2.4.1)
17
+ rainbow (3.1.1)
18
+ rake (13.0.6)
19
+ regexp_parser (2.5.0)
20
+ rexml (3.2.5)
21
+ rspec (3.11.0)
22
+ rspec-core (~> 3.11.0)
23
+ rspec-expectations (~> 3.11.0)
24
+ rspec-mocks (~> 3.11.0)
25
+ rspec-core (3.11.0)
26
+ rspec-support (~> 3.11.0)
27
+ rspec-expectations (3.11.0)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.11.0)
30
+ rspec-mocks (3.11.1)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.11.0)
33
+ rspec-support (3.11.0)
34
+ rubocop (1.31.2)
35
+ json (~> 2.3)
36
+ parallel (~> 1.10)
37
+ parser (>= 3.1.0.0)
38
+ rainbow (>= 2.2.2, < 4.0)
39
+ regexp_parser (>= 1.8, < 3.0)
40
+ rexml (>= 3.2.5, < 4.0)
41
+ rubocop-ast (>= 1.18.0, < 2.0)
42
+ ruby-progressbar (~> 1.7)
43
+ unicode-display_width (>= 1.4.0, < 3.0)
44
+ rubocop-ast (1.19.1)
45
+ parser (>= 3.1.1.0)
46
+ ruby-progressbar (1.11.0)
47
+ unicode-display_width (2.2.0)
48
+ webrick (1.7.0)
49
+ yard (0.9.28)
50
+ webrick (~> 1.7.0)
51
+
52
+ PLATFORMS
53
+ arm64-darwin-21
54
+ ruby
55
+ x86_64-linux
56
+
57
+ DEPENDENCIES
58
+ concurrent-ruby (~> 1.1)
59
+ mediate!
60
+ rake (~> 13.0)
61
+ rspec (~> 3.0)
62
+ rubocop (~> 1.21)
63
+ yard (~> 0.9.28)
64
+
65
+ BUNDLED WITH
66
+ 2.3.17
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # Mediate
2
+
3
+ A simple mediator implementation for Ruby inspired by [Mediatr](https://github.com/jbogard/MediatR).
4
+
5
+ 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.
6
+
7
+ Supports request/response, notifications (i.e., events), pre- and post-request handler decorators, and error handling.
8
+
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [Requests](#requests)
12
+ - [Implicit handler declaration](#implicit-handler-declaration)
13
+ - [Request polymorphism](#request-polymorphism)
14
+ - [Pre- and post-request behaviors](#pre--and-post-request-behaviors)
15
+ - [Notifications](#notifications)
16
+ - [Error handlers](#error-handlers)
17
+ - [Testing](#testing)
18
+ - [Testing implicit request handlers](#testing-implicit-request-handlers)
19
+ - [Using with Rails](#using-with-rails)
20
+ - [Development](#development)
21
+ - [Contributing](#contributing)
22
+ - [License](#license)
23
+
24
+ ## Installation
25
+
26
+ Add this to your Gemfile:
27
+
28
+ ```ruby
29
+ gem "mediate"
30
+ ```
31
+
32
+ And run:
33
+
34
+ ```sh
35
+ bundle
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ There are two types of messages that can be sent through the mediator:
41
+
42
+ - Requests (`Mediate::Request`) have exactly one handler (`Mediate::RequestHandler`), which returns a response.
43
+ - Notifications (`Mediate::Notification`) are `publish`ed to zero or more handlers (`Mediate::NotificationHandler`). Nothing is returned to the caller.
44
+
45
+ ### Requests
46
+
47
+ To define a request, declare a class that inherits from `Mediate::Request`.
48
+
49
+ ```ruby
50
+ class Ping < Mediate::Request
51
+ attr_reader :message
52
+
53
+ def initialize(message)
54
+ @message = message
55
+ super()
56
+ end
57
+ end
58
+ ```
59
+
60
+ To register a handler for it, declare a class that inherits from `Mediate::RequestHandler`, call the class method `handles` passing the class of requests that it handles, and implement the `handle` method.
61
+
62
+ ```ruby
63
+ class PingHandler < Mediate::RequestHandler
64
+ handles Ping
65
+
66
+ def handle(request)
67
+ "Received: #{request.message}"
68
+ end
69
+ end
70
+ ```
71
+
72
+ To send a request, pass it to `Mediate.dispatch`. The mediator will resolve the registered handler according to the request type and return the result of its `handle` method.
73
+
74
+ ```ruby
75
+ response = Mediate.dispatch(Ping.new('hello'))
76
+ puts response # 'Received: hello'
77
+ ```
78
+
79
+ The only requirement for `RequestHandler`s, besides implementing the `handle` method, is that __they should have a constructor that can be called without arguments__. This applies to all `*Handler` and `*Behavior` classes. For example, the following would work because all constructor parameters have default values.
80
+
81
+ ```ruby
82
+ class PingHandler < Mediate::RequestHandler
83
+ handles Ping
84
+
85
+ def initialize(service = SomeService.new)
86
+ @service = service
87
+ end
88
+
89
+ def handle(request)
90
+ @service.call("Received: #{request.message}")
91
+ end
92
+ end
93
+ ```
94
+
95
+ Note that only one handler can be registered for a particular request class; attempting to register another handler for `Ping` would raise a `RequestHandlerAlreadyExistsError`.
96
+
97
+ #### Implicit handler declaration
98
+
99
+ For simple handlers, you can skip the explicit `RequestHandler` declaration above and instead pass a block to `Request.handle_with`.
100
+
101
+ ```ruby
102
+ class Ping < Mediate::Request
103
+ attr_reader :message
104
+
105
+ def initialize(message)
106
+ @message = message
107
+ super()
108
+ end
109
+ # This will have the same behavior as the PingHandler declaration above.
110
+ handle_with { |request| "Received: #{request.message}" }
111
+ end
112
+
113
+ response = Mediate.dispatch(Ping.new('hello'))
114
+ puts response # 'Received: hello'
115
+ ```
116
+
117
+ Behind the scenes, this defines a `Ping::Handler` class that calls the given block 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
+
119
+ #### Request polymorphism
120
+
121
+ The mediator resolves handlers by moving up the request's inheritance chain until it finds a registered handler for that class. For example, subclasses of `Ping` would be handled by `PingHandler`.
122
+
123
+ ```ruby
124
+ class SubPing < Ping; end
125
+ puts Mediate.dispatch(SubPing.new('howdy')) # 'Received: howdy'
126
+ ```
127
+
128
+ Unless we registered a handler for `SubPing` explicitly.
129
+
130
+ ```ruby
131
+ class SubPing < Ping
132
+ handle_with { |request| "Received from SubPing: #{request.message}" }
133
+ end
134
+ puts Mediate.dispatch(SubPing.new('howdy')) # 'Received from SubPing: howdy'
135
+ ```
136
+
137
+ #### Pre- and post-request behaviors
138
+
139
+ For certain cases, you will want code to run before or after a request is handled, e.g., logging, authorization, validation, backwards compatibility, etc. Effectively, these act as decorators for your request handler(s). You can register `Mediate::PrerequestBehavior`s and `Mediate::PostrequestBehavior`s for this purpose.
140
+
141
+ Behaviors will run for any request that is or inherits from the request class registered. For example, if you wanted a behavior to run for every request, you could register it with `handles Mediate::Request`. Unlike request handlers, multiple behaviors can be registered for the same request class.
142
+
143
+ ```ruby
144
+ class PreLoggingBehavior < Mediate::PrerequestBehavior
145
+ handles Mediate::Request # This will be called before all request handlers
146
+
147
+ def initialize(logger = Logger)
148
+ @logger = logger
149
+ end
150
+
151
+ def handle(request)
152
+ @logger.info("Received request: #{request}")
153
+ end
154
+ end
155
+
156
+ class PingValidator < Mediate::PrerequestBehavior
157
+ handles Ping # Will be called before Ping requests or any subclasses of Ping
158
+
159
+ def handle(request)
160
+ raise "Ping is missing message" if request.message.nil?
161
+ end
162
+ end
163
+
164
+ class PostLoggingBehavior < Mediate::PostrequestBehavior
165
+ handles Mediate::Request # Will be called after all request handlers
166
+
167
+ def initialize(logger = Logger)
168
+ @logger = logger
169
+ end
170
+
171
+ def handle(request, result)
172
+ @logger.info("Request: #{request} resulted in #{result}")
173
+ end
174
+ end
175
+ ```
176
+
177
+ ### Notifications
178
+
179
+ Notifications are messages that can be passed to multiple handlers. To publish a notification, call `Mediate.publish(notification)`. No response is returned from `publish`.
180
+
181
+ Define a notification by inheriting from `Mediate::Notification`.
182
+
183
+ ```ruby
184
+ class PostCreated < Mediate::Notification
185
+ attr_reader :post
186
+
187
+ def initialize(post)
188
+ @post = post
189
+ end
190
+ end
191
+ ```
192
+
193
+ Declare and register a handler by inheriting from `Mediate::NotificationHandler`, calling `handles` with the notification class to handle, and implementing the `handle` method.
194
+
195
+ ```ruby
196
+ class PostCreatedHandler < Mediate::NotificationHandler
197
+ handles PostCreated
198
+
199
+ def handle(notification)
200
+ # do something with PostCreated notification...
201
+ end
202
+ end
203
+ ```
204
+
205
+ Like [request behaviors](#pre--and-post-request-behaviors), all notification handlers that are registered for a notification class or any of its superclasses will be called when a given notification is published. For example, a handler that `handles Mediate::Notification` will be called when any notification is published. Handlers will be called in order of inheritance of their registered notifications from subclass to superclass (and in order of registration if the registered notification class is the same).
206
+
207
+ ### Error handlers
208
+
209
+ When a request or notification handler raises a `StandardError`, the mediator will find all `ErrorHandler`s that have been registered for that request/notification class (or superclasses) and the exception class (or superclasses).
210
+
211
+ ```ruby
212
+ # This will be called on any StandardError from any request or notification handler
213
+ class GlobalErrorHandler < Mediate::ErrorHandler
214
+ handles StandardError, Mediate::Request
215
+ handles StandardError, Mediate::Notification
216
+
217
+ # dispatched is the Request or Notification
218
+ def handle(dispatched, exception, state)
219
+ # do something...
220
+ end
221
+ end
222
+
223
+ # This would get called when ActiveRecord::RecordNotFound is raised while handling a QueryRequest
224
+ class NotFoundHandler < Mediate::ErrorHandler
225
+ handles ActiveRecord::RecordNotFound, QueryRequest
226
+
227
+ def handle(dispatched, exception, state)
228
+ # ...
229
+ end
230
+ end
231
+ ```
232
+
233
+ Note that the exception class passed to handles must be `StandardError` or a subclass of it.
234
+
235
+ The `state` parameter of `handle` is a `Mediate::ErrorHandlerState` instance that represents whether the exception has been "handled" or not. By calling `set_as_handled` and optionally passing in a result, all subsequent error handlers will be skipped and the given result will be returned to the caller of `dispatch` (obviously, if the error was raised from a notification handler, nothing will be returned).
236
+
237
+ ```ruby
238
+ class ValidationErrorHandler < Mediate::ErrorHandler
239
+ handles ActiveRecord::RecordInvalid, Mediate::Request
240
+
241
+ def handle(dispatched, exception, state)
242
+ state.set_as_handled(exception.record.errors)
243
+ end
244
+ end
245
+ ```
246
+
247
+ ### Testing
248
+
249
+ All of the handler and behavior classes described above are just normal Ruby classes. You can instantiate them and call their `handle` methods to test as you normally would.
250
+
251
+ Special consideration is only required when testing paths that invoke methods on the mediator itself (e.g., `Mediate.dispatch` or `Mediate.publish`), since it is designed to be a singleton. The mediator's registration methods are idempotent (and thread-safe), so re-registering handlers should not cause issues. However, if you want to ensure that you are not sharing state between tests, you can call the `Mediate.mediator.reset` method in your test setup or clean-up to remove all handler and behavior registrations.
252
+
253
+ #### Testing implicit request handlers
254
+
255
+ How can you test a request handler defined using `handle_with` and a block like the following?
256
+
257
+ ```ruby
258
+ class ExampleRequest < Mediate::Request
259
+ handle_with do |request|
260
+ # ....
261
+ end
262
+ end
263
+ ```
264
+
265
+ The `handle_with` method defines a handler class and registers it with the mediator to handle the containing request class. `Mediate::Request` provides a convenience method, `create_implicit_handler`, that creates an instance of this handler class. You can then call `handle` on that method like normal to test it.
266
+
267
+ ```ruby
268
+ RSpec.describe "ExampleRequestHandler" do
269
+ let(:handler) { ExampleRequest.create_implicit_handler }
270
+
271
+ it "returns something" do
272
+ expect(handler.handle(ExampleRequest.new)).to be_truthy
273
+ end
274
+ end
275
+ ```
276
+
277
+ ### Using with Rails
278
+
279
+ TODO
280
+
281
+ ## Development
282
+
283
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
284
+
285
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
286
+
287
+ ## Contributing
288
+
289
+ Bug reports and pull requests are welcome in this repo.
290
+
291
+ ## License
292
+
293
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ require "yard"
13
+
14
+ YARD::Rake::YardocTask.new
15
+
16
+ task default: %i[spec rubocop]
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # @abstract override {#handle} to implement.
6
+ #
7
+ # An abstract base class that handles exceptions raised by request handlers, behaviors, or notification handlers.
8
+ #
9
+ class ErrorHandler
10
+ #
11
+ # Registers this to handle exceptions of type exception_class when raised while handling requests or notifications
12
+ # of type dispatched_class.
13
+ #
14
+ # @param [StandardError] exception_class the type of exceptions that this should handle
15
+ # @param [Class] dispatched_class the request or notification type
16
+ # (should inherit from Mediate::Request or Mediate::Notification)
17
+ # @param [Mediate::Mediator] mediator the Mediator instance to register on
18
+ #
19
+ # @return [void]
20
+ #
21
+ # @raise [ArgumentError] if exception_class is not a StandardError
22
+ # or dispatched_class is not a Request or Notification
23
+ def self.handles(exception_class = StandardError, dispatched_class = Mediate::Request, mediator = Mediate.mediator)
24
+ mediator.register_error_handler(self, exception_class, dispatched_class)
25
+ end
26
+
27
+ #
28
+ # The method to implement to handle exceptions.
29
+ #
30
+ # @abstract
31
+ #
32
+ # @param [Mediate::Request, Mediate::Notification] _dispatched the request or notification that was been handled
33
+ # @param [StandardError] _exception the exception that was raised
34
+ # @param [Mediate::ErrorHandlerState] _state the result of handling the current exception--
35
+ # call state.set_as_handled(result) to skip subsequent error handlers and return result
36
+ # (if the exception was thrown while handling a request; notification handlers will not return anything)
37
+ #
38
+ # @return [void]
39
+ #
40
+ def handle(_dispatched, _exception, _state)
41
+ raise NoMethodError, "handle must be implemented"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # Represents the result of handling an exception
6
+ #
7
+ class ErrorHandlerState
8
+ attr_reader :result
9
+
10
+ #
11
+ # Sets the state as handled with the given result. Subsequent error handlers will be skipped and, if
12
+ # the exception was thrown while handling a Mediate::Request, this result will be returned.
13
+ #
14
+ # @param result if the exception was thrown as part of handling a Mediate::Request, this will be returned
15
+ #
16
+ # @return [Boolean] true
17
+ #
18
+ def set_as_handled(result = nil)
19
+ @result = result
20
+ @handled = true
21
+ end
22
+
23
+ #
24
+ # Indicates whether the current exception has been handled and the result should be returned (if applicable).
25
+ #
26
+ # @return [Boolean] whether the current exception has been handled
27
+ #
28
+ def handled?
29
+ !!@handled
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # Namespace that contains custom Mediate errors.
6
+ #
7
+ module Errors
8
+ #
9
+ # Indicates that a Mediate::Request was sent to the Mediator, but no Mediate::RequestHandler
10
+ # was registered to handle it.
11
+ #
12
+ class NoHandlerError < StandardError
13
+ def initialize(request_class)
14
+ super("No handler for #{request_class}. Call handles(#{request_class}) on a RequestHandler to register.")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # Namespace that contains custom Mediate errors.
6
+ #
7
+ module Errors
8
+ #
9
+ # Indicates that a Mediate::RequestHandler was already registered for the given request_class.
10
+ #
11
+ class RequestHandlerAlreadyExistsError < StandardError
12
+ def initialize(request_class, registered_handler_class, attempted_handler_class)
13
+ super("Attempted to register #{attempted_handler_class} to handle #{request_class},\
14
+ but #{registered_handler_class} is already registered to handle #{request_class}.\
15
+ This is probably a mistake, as only one handler should be registered per request type.")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+ require "singleton"
5
+
6
+ require_relative "errors/no_handler_error"
7
+ require_relative "errors/request_handler_already_exists_error"
8
+ require_relative "error_handler"
9
+ require_relative "error_handler_state"
10
+ require_relative "notification"
11
+ require_relative "notification_handler"
12
+ require_relative "postrequest_behavior"
13
+ require_relative "prerequest_behavior"
14
+ require_relative "request"
15
+ require_relative "request_handler"
16
+
17
+ module Mediate
18
+ #
19
+ # Implements the mediator pattern. Call {#dispatch} to send requests and
20
+ # {#publish} to publish notifications.
21
+ #
22
+ class Mediator
23
+ include Singleton
24
+ REQUEST_BASE = Mediate::Request
25
+ NOTIF_BASE = Mediate::Notification
26
+ private_constant :REQUEST_BASE, :NOTIF_BASE
27
+
28
+ def initialize
29
+ reset
30
+ end
31
+
32
+ #
33
+ # Sends a request to the registered handler, passing to any applicable pipeline behaviors.
34
+ #
35
+ # @param [Mediate::Request] request
36
+ #
37
+ # @return the response returned from the Mediate::RequestHandler
38
+ #
39
+ # @raise [ArgumentError] if request is nil
40
+ # @raise [Mediate::Errors::NoHandlerError] if no handlers have been registered for the given request's type
41
+ #
42
+ def dispatch(request)
43
+ raise ArgumentError, "request cannot be nil" if request.nil?
44
+
45
+ request_handler = resolve_handler(@request_handlers, request.class, REQUEST_BASE)
46
+ raise Errors::NoHandlerError, request.class if request_handler == NullHandler
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)
51
+ end
52
+
53
+ #
54
+ # Sends a notification to all register handlers for the given notification's type.
55
+ #
56
+ # @param [Mediate::Notification] notification
57
+ #
58
+ # @return [void]
59
+ #
60
+ def publish(notification)
61
+ raise ArgumentError, "notification cannot be nil" if notification.nil?
62
+
63
+ handler_classes = collect_by_inheritance(@notification_handlers, notification.class, NOTIF_BASE)
64
+ handler_classes.each do |handler_class|
65
+ handler_class.new.handle(notification)
66
+ rescue StandardError => e
67
+ handle_exception(notification, e, NOTIF_BASE)
68
+ # Don't break from loop, since we don't want a single notification handler to prevent others from
69
+ # receiving notification.
70
+ end
71
+ end
72
+
73
+ def register_request_handler(handler_class, request_class)
74
+ validate_base_class(handler_class, Mediate::RequestHandler)
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
78
+ end
79
+
80
+ def register_notification_handler(handler_class, notif_class)
81
+ validate_base_class(handler_class, Mediate::NotificationHandler)
82
+ validate_base_class(notif_class, NOTIF_BASE, allow_base: true)
83
+ append_to_hash_value(@notification_handlers, notif_class, handler_class)
84
+ end
85
+
86
+ def register_prerequest_behavior(behavior_class, request_class)
87
+ validate_base_class(behavior_class, Mediate::PrerequestBehavior)
88
+ validate_base_class(request_class, REQUEST_BASE, allow_base: true)
89
+ append_to_hash_value(@prerequest_behaviors, request_class, behavior_class)
90
+ end
91
+
92
+ def register_postrequest_behavior(behavior_class, request_class)
93
+ validate_base_class(behavior_class, Mediate::PostrequestBehavior)
94
+ validate_base_class(request_class, REQUEST_BASE, allow_base: true)
95
+ append_to_hash_value(@postrequest_behaviors, request_class, behavior_class)
96
+ end
97
+
98
+ def register_error_handler(handler_class, exception_class, dispatch_class)
99
+ if dispatch_class <= NOTIF_BASE
100
+ register_error_handler_for_dispatch(handler_class, exception_class, dispatch_class, NOTIF_BASE)
101
+ else
102
+ register_error_handler_for_dispatch(handler_class, exception_class, dispatch_class, REQUEST_BASE)
103
+ end
104
+ end
105
+
106
+ #
107
+ # Clears all registered handlers and behaviors for this Mediator instance. This is useful
108
+ # for cleaning up after integration tests.
109
+ #
110
+ # @return [void]
111
+ #
112
+ def reset
113
+ @request_handlers = Concurrent::Map.new
114
+ @notification_handlers = Concurrent::Map.new
115
+ @prerequest_behaviors = Concurrent::Map.new
116
+ @postrequest_behaviors = Concurrent::Map.new
117
+ @exception_handlers = Concurrent::Map.new
118
+ end
119
+
120
+ private
121
+
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
128
+
129
+ def register_error_handler_for_dispatch(handler_class, exception_class, dispatch_class, dispatch_base_class)
130
+ validate_base_class(handler_class, Mediate::ErrorHandler)
131
+ validate_base_class(exception_class, StandardError, allow_base: true)
132
+ validate_base_class(dispatch_class, dispatch_base_class, allow_base: true)
133
+ map = @exception_handlers.fetch_or_store(exception_class, Concurrent::Map.new)
134
+ append_to_hash_value(map, dispatch_class, handler_class)
135
+ end
136
+
137
+ def run_request_pipeline(request, pre_handlers, request_handler, post_handlers)
138
+ result = nil
139
+ pre_handlers.each { |handler_class| handler_class.new.handle(request) }
140
+ result = request_handler.new.handle(request)
141
+ post_handlers.each { |handler_class| handler_class.new.handle(request, result) }
142
+ result
143
+ rescue StandardError => e
144
+ handle_exception(request, e, REQUEST_BASE)
145
+ end
146
+
147
+ def append_to_hash_value(hash, key, value)
148
+ hash[key] = hash.fetch(key, Concurrent::Set.new) << value
149
+ end
150
+
151
+ def validate_base_class(given, expected_base, allow_base: false)
152
+ raise ArgumentError, "class cannot be nil" if given.nil? || expected_base.nil?
153
+
154
+ return if allow_base && given == expected_base
155
+
156
+ raise ArgumentError, "#{given} does not inherit from #{expected_base}" unless given < expected_base
157
+ end
158
+
159
+ def handle_exception(dispatched, exception, dispatch_base_class)
160
+ exception_to_dispatched_maps = collect_by_inheritance(@exception_handlers, exception.class, StandardError)
161
+ handler_classes = exception_to_dispatched_maps.reduce(Concurrent::Set.new) do |memo, curr|
162
+ collect_by_inheritance(curr, dispatched.class, dispatch_base_class, memo)
163
+ end
164
+ state = ErrorHandlerState.new
165
+ handler_classes.each do |handler_class|
166
+ handler_class.new.handle(dispatched, exception, state)
167
+ break if state.handled?
168
+ end
169
+ state.result
170
+ end
171
+
172
+ def resolve_handler(handlers_hash, request_class, base_class)
173
+ value = handlers_hash[request_class]
174
+ return value unless value.nil?
175
+ return NullHandler if request_class >= base_class
176
+
177
+ resolve_handler(handlers_hash, request_class.superclass, base_class)
178
+ end
179
+
180
+ def collect_by_inheritance(hash, key_class, base_class, collected = Concurrent::Set.new)
181
+ values = hash.fetch(key_class, Concurrent::Set.new)
182
+ # Wrap values in Set and flatten to account for case when values is not a Set itself.
183
+ # This may break if values is nested Sets, although we don't have that case yet.
184
+ new_collected = (collected || Concurrent::Set.new) | Concurrent::Set[values].flatten
185
+ return new_collected if key_class > base_class
186
+
187
+ collect_by_inheritance(hash, key_class.superclass, base_class, new_collected)
188
+ end
189
+
190
+ #
191
+ # A null object for a Mediate::RequestHandler
192
+ #
193
+ class NullHandler; end
194
+ end
195
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # The base class of a notification that can be published to multiple handlers.
6
+ #
7
+ class Notification
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # @abstract override {#handle} to implement.
6
+ #
7
+ # Abstract base class of a handler of notifications.
8
+ #
9
+ class NotificationHandler
10
+ #
11
+ # Registers this handler for the given notification Class.
12
+ # The notification Class must have Mediate::Notification as a superclass.
13
+ #
14
+ # @param [Class] notif_class the Class of the notifications that get passed to this handler
15
+ # @param [Mediate::Mediator] mediator the mediator instance to register the handler with
16
+ #
17
+ # @raise [ArgumentError] if notif_class does not inherit from Mediate::Notification
18
+ #
19
+ # @return [void]
20
+ #
21
+ def self.handles(notif_class = Mediate::Notification, mediator = Mediate.mediator)
22
+ mediator.register_notification_handler(self, notif_class)
23
+ end
24
+
25
+ #
26
+ # The method to implement that handles the notifications registered for this handler.
27
+ #
28
+ # @abstract
29
+ #
30
+ # @param [Mediate::Notification] _notification
31
+ #
32
+ # @return [void]
33
+ #
34
+ def handle(_notification)
35
+ raise NoMethodError, "handle must be implemented"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # @abstract override {#handle} to implement
6
+ #
7
+ # Abstract base class of a pipeline behavior that processes a request after the RequestHandler finishes.
8
+ #
9
+ class PostrequestBehavior
10
+ #
11
+ # Registers this behavior to handle requests of the given type.
12
+ #
13
+ # @param [Class] request_class the type of requests to handle (should inherit from Mediate::Request)
14
+ # @param [Mediate::Mediator] mediator the Mediator instance to register to
15
+ #
16
+ # @return [void]
17
+ #
18
+ # @raise [ArgumentError] if request_class does not inherit from Mediate::Request
19
+ #
20
+ def self.handles(request_class = Mediate::Request, mediator = Mediate.mediator)
21
+ mediator.register_postrequest_behavior(self, request_class)
22
+ end
23
+
24
+ #
25
+ # @abstract
26
+ #
27
+ # The method that handles the request and the result that the handler returned.
28
+ #
29
+ # @param [Mediate::Request] _request
30
+ # @param _result what was returned by the RequestHandler
31
+ #
32
+ # @return [void]
33
+ #
34
+ def handle(_request, _result)
35
+ raise NoMethodError, "handle must be implemented"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # @abstract override {#handle} to implement
6
+ #
7
+ # Abstract base class of a pipeline behavior that processes a request before it's given to the handler.
8
+ #
9
+ class PrerequestBehavior
10
+ #
11
+ # Registers this behavior to handle requests of the given type.
12
+ #
13
+ # @param [Class] request_class the type of requests to handle (should inherit from Mediate::Request)
14
+ # @param [Mediate::Mediator] mediator the Mediator instance to register to
15
+ #
16
+ # @return [void]
17
+ #
18
+ # @raise [ArgumentError] if request_class does not inherit from Mediate::Request
19
+ #
20
+ def self.handles(request_class = Mediate::Request, mediator = Mediate.mediator)
21
+ mediator.register_prerequest_behavior(self, request_class)
22
+ end
23
+
24
+ #
25
+ # @abstract
26
+ #
27
+ # The method that handles the request.
28
+ #
29
+ # @param [Mediate::Request] _request
30
+ #
31
+ # @return [void]
32
+ #
33
+ def handle(_request)
34
+ raise NoMethodError, "handle must be implemented"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # The base class of a request that can be dispatched to the mediator, which
6
+ # returns a response from its handler.
7
+ #
8
+ class Request
9
+ IMPLICIT_HANDLER_CLASS_NAME = "Handler"
10
+
11
+ #
12
+ # Registers a handler for this Request type using the given block as the handle method.
13
+ #
14
+ # @param [Mediate::Mediator] mediator the instance to register the handler on
15
+ # @param [Proc] &proc the block that will handle the request
16
+ #
17
+ # @raises [ArgumentError] if no block is given
18
+ #
19
+ # @example When a request of this type is dispatched, the handle_with block will run
20
+ # class MyRequest < Mediate::Request
21
+ # handle_with do |request|
22
+ # ## do something with request...
23
+ # end
24
+ # end
25
+ def self.handle_with(mediator = Mediate.mediator, &proc)
26
+ raise ArgumentError, "expected block to be passed to #handle_with." unless proc
27
+
28
+ if implicit_handler_defined?
29
+ raise "#{name}::#{IMPLICIT_HANDLER_CLASS_NAME} is already defined. Cannot create implicit handler."
30
+ end
31
+
32
+ handler_class = define_handler(proc)
33
+ const_set(IMPLICIT_HANDLER_CLASS_NAME, handler_class)
34
+ mediator.register_request_handler(handler_class, self)
35
+ end
36
+
37
+ #
38
+ # If an implicit handler is defined for this Request using the #handle_with method,
39
+ # this will return an instance of it. Use this for testing the handler.
40
+ #
41
+ # @return [Mediate::RequestHandler] the implicit handler class for this Request
42
+ #
43
+ # @raise [RuntimeError] if no implicit handler is defined
44
+ #
45
+ # @example Create an instance and call #handle on it to test it
46
+ # handler = MyRequest.create_implicit_handler
47
+ # result = handler.handle(MyRequest.new)
48
+ def self.create_implicit_handler
49
+ raise "Implicit handler is not defined." unless implicit_handler_defined?
50
+
51
+ const_get(IMPLICIT_HANDLER_CLASS_NAME).new
52
+ end
53
+
54
+ def self.undefine_implicit_handler
55
+ return unless implicit_handler_defined?
56
+
57
+ remove_const(IMPLICIT_HANDLER_CLASS_NAME)
58
+ end
59
+
60
+ def self.define_handler(proc)
61
+ Class.new(RequestHandler) do
62
+ @@handle_proc = proc # rubocop:disable Style/ClassVars
63
+ def handle(request)
64
+ @@handle_proc.call(request)
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.implicit_handler_defined?
70
+ const_defined?(IMPLICIT_HANDLER_CLASS_NAME)
71
+ end
72
+
73
+ private_constant :IMPLICIT_HANDLER_CLASS_NAME
74
+ private_class_method :define_handler, :implicit_handler_defined?
75
+ end
76
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ #
5
+ # @abstract override {#handle} to implement.
6
+ #
7
+ # Abstract base class of a handler of requests. Each request type should have only one
8
+ # handler.
9
+ #
10
+ class RequestHandler
11
+ #
12
+ # Registers this handler for the given request Class.
13
+ # The request Class must have Mediate::Request as a superclass.
14
+ #
15
+ # @param [Class] request_class the Class of the requests that get passed to this handler
16
+ # @param [Mediate::Mediator] mediator the mediator instance to register the handler with
17
+ #
18
+ # @raise [ArgumentError] if request_class does not inherit from Mediate::Request
19
+ #
20
+ # @return [void]
21
+ #
22
+ def self.handles(request_class, mediator = Mediate.mediator)
23
+ mediator.register_request_handler(self, request_class)
24
+ end
25
+
26
+ #
27
+ # The method to implement that handles the request of the type registered
28
+ # for this handler. Whatever this method returns will be returned to the sender of the request.
29
+ #
30
+ # @abstract
31
+ #
32
+ # @param [Mediate::Request] _request
33
+ #
34
+ # @return the result of handling the request
35
+ #
36
+ def handle(_request)
37
+ raise NoMethodError, "handle must be implemented"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mediate
4
+ VERSION = "0.1.0"
5
+ end
data/lib/mediate.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mediate/version"
4
+ require_relative "mediate/mediator"
5
+ require "singleton"
6
+
7
+ #
8
+ # Namespace containing a simple implementation of the mediator pattern.
9
+ #
10
+ module Mediate
11
+ #
12
+ # Get the current mediator instance. This will be a singleton
13
+ # throughout the lifetime of the program.
14
+ #
15
+ # @return [Mediate::Mediator] the current mediator instance
16
+ #
17
+ def self.mediator
18
+ Mediator.instance
19
+ end
20
+
21
+ #
22
+ # Sends a request to the registered handler, passing to any applicable pipeline behaviors.
23
+ #
24
+ # @param [Mediate::Request] request
25
+ #
26
+ # @return the response returned from the Mediate::RequestHandler
27
+ #
28
+ # @raise [ArgumentError] if request is nil
29
+ # @raise [Mediate::Errors::NoHandlerError] if no handlers have been registered for the given request's type
30
+ #
31
+ def self.dispatch(request)
32
+ mediator.dispatch(request)
33
+ end
34
+
35
+ #
36
+ # Sends a notification to all register handlers for the given notification's type.
37
+ #
38
+ # @param [Mediate::Notification] notification
39
+ #
40
+ # @return [void]
41
+ #
42
+ def self.publish(notification)
43
+ mediator.publish(notification)
44
+ end
45
+ end
data/mediate.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mediate/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mediate"
7
+ spec.version = Mediate::VERSION
8
+ spec.authors = ["Ryan Ferguson"]
9
+
10
+ spec.summary = "Simple mediator implementation"
11
+ spec.homepage = "https://github.com/rferg/mediate"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 2.6.0"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/rferg/mediate"
17
+ spec.metadata["rubygems_mfa_required"] = "true"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "concurrent-ruby", "~> 1.1"
31
+
32
+ # For more information and examples about making a new gem, check out our
33
+ # guide at: https://bundler.io/guides/creating_gem.html
34
+ end
data/sig/mediate.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Mediate
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mediate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Ferguson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ description:
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".rspec"
34
+ - ".rubocop.yml"
35
+ - ".vscode/extensions.json"
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - lib/mediate.rb
42
+ - lib/mediate/error_handler.rb
43
+ - lib/mediate/error_handler_state.rb
44
+ - lib/mediate/errors/no_handler_error.rb
45
+ - lib/mediate/errors/request_handler_already_exists_error.rb
46
+ - lib/mediate/mediator.rb
47
+ - lib/mediate/notification.rb
48
+ - lib/mediate/notification_handler.rb
49
+ - lib/mediate/postrequest_behavior.rb
50
+ - lib/mediate/prerequest_behavior.rb
51
+ - lib/mediate/request.rb
52
+ - lib/mediate/request_handler.rb
53
+ - lib/mediate/version.rb
54
+ - mediate.gemspec
55
+ - sig/mediate.rbs
56
+ homepage: https://github.com/rferg/mediate
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/rferg/mediate
61
+ source_code_uri: https://github.com/rferg/mediate
62
+ rubygems_mfa_required: 'true'
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.6.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.3.7
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Simple mediator implementation
82
+ test_files: []