medi8-rb 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: d97412258c38426f13a8e52fb52454a37a38228e2d466b285b65c205f7408424
4
+ data.tar.gz: e5f88badb5678bfd8d5a5fdc1d27c51b249cbbf1d673f03842db241adeb91bce
5
+ SHA512:
6
+ metadata.gz: 45fa60fb8b43af3fcd1c2ef7aad298e2a8948ab5355dc0078e6205f39fbde87a14d7044e8879a1eb4ba52fb8c0fd4d4fef8b72dec549e4d99a44624846ca81c5
7
+ data.tar.gz: 1aadd7477bdb056b0dd8d3bff61708194dad02de6c541362dc6086f2b073a6b626c84c59693759bac6dc1c0e76cd8a1a9867d5f78c1c93e17a2472ad063db465
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # Medi8-rb
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/medi8-rb.svg)](https://badge.fury.io/rb/medi8-rb)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ Medi8 is a lightweight, idiomatic mediator pattern implementation for Ruby and Rails, inspired by MediatR (from .NET)
7
+
8
+ Medi8 is not a 1:1 Ruby analog of MediatR, but it faithfully implements its core principles in an idiomatic Ruby way.
9
+
10
+ Medi8 is compatible with pure Ruby, Sinatra, and Rails.
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - Simple `send` method for commands and queries
17
+ - `publish` notifications to many subscribers
18
+ - Middleware pipeline
19
+ - Async notifications with ActiveJob
20
+ - Rails integration via `Railtie`
21
+ - Fully modular, no inheritance required
22
+
23
+ ---
24
+
25
+ ## Design
26
+
27
+ ```plantuml
28
+ @startuml
29
+ title Medi8 Logical Flow and Event Sequence
30
+
31
+ actor "Caller (e.g. Controller)" as Caller
32
+ participant "Medi8" as Medi8
33
+ participant "Mediator" as Mediator
34
+ participant "RegisterUserHandler" as Handler
35
+ participant "Medi8.publish" as Publisher
36
+ participant "SendWelcomeEmailHandler" as Emailer
37
+ participant "TrackRegistrationHandler" as Tracker
38
+
39
+ == Command Dispatch ==
40
+
41
+ Caller -> Medi8: send(RegisterUser)
42
+ Medi8 -> Mediator: new(registry)\nsend(request)
43
+ Mediator -> Handler: call(request)
44
+
45
+ == Event Publishing ==
46
+
47
+ Handler -> Medi8: publish(UserRegistered)
48
+ Medi8 -> Mediator: new(registry)\npublish(event)
49
+ Mediator -> Publisher: NotificationDispatcher.publish(event)
50
+
51
+ == Notify Subscribers ==
52
+
53
+ Publisher -> Emailer: call(event)
54
+ Publisher -> Tracker: call(event)
55
+
56
+ @enduml
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Installation
62
+
63
+ Add this line to your Gemfile:
64
+
65
+ ```ruby
66
+ gem "medi8-rb"
67
+ ```
68
+
69
+ Then bundle:
70
+
71
+ ```bash
72
+ $ bundle install
73
+ ```
74
+
75
+ Or install it directly:
76
+
77
+ ```bash
78
+ $ gem install medi8-rb
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Concept
84
+
85
+ Use `Medi8.send(request)` for command/query behavior, and `Medi8.publish(event)` for notifications. Handlers are registered with a simple DSL.
86
+
87
+ ---
88
+
89
+ ## Configuration
90
+
91
+ Set up in an initializer:
92
+
93
+ ```ruby
94
+ # config/initializers/medi8.rb
95
+ Medi8.configure do |config|
96
+ config.use AwesomeMiddleware
97
+ end
98
+ ```
99
+
100
+ Example middleware:
101
+
102
+ ```ruby
103
+ class AwesomeMiddleware
104
+ def call(request)
105
+ Rails.logger.info("Processing #{request.class.name}")
106
+ yield
107
+ end
108
+ end
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Usage
114
+
115
+ Medi8 uses a simple request/handler model. You define a request class, register a handler using handles, and then invoke `Medi8.send(request)`.
116
+
117
+ ### Create a Request
118
+
119
+ ```ruby
120
+ # app/requests/create_user.rb
121
+ class CreateUser
122
+ attr_reader :name
123
+
124
+ def initialize(name:)
125
+ @name = name
126
+ end
127
+ end
128
+ ```
129
+
130
+ ### Create a Handler
131
+
132
+ ```ruby
133
+ # app/handlers/create_user_handler.rb
134
+ class CreateUserHandler
135
+ include Medi8::Handler
136
+
137
+ handles CreateUser
138
+
139
+ def call(request)
140
+ User.create!(name: request.name)
141
+ end
142
+ end
143
+ ```
144
+
145
+ ### Send
146
+
147
+ ```ruby
148
+ Medi8.send(CreateUser.new(name: "Alice"))
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Notifications
154
+
155
+ ### Define an Event
156
+
157
+ ```ruby
158
+ class UserRegistered
159
+ attr_reader :user_id
160
+
161
+ def initialize(user_id:)
162
+ @user_id = user_id
163
+ end
164
+ end
165
+ ```
166
+
167
+ ### Define a Subscriber
168
+
169
+ ```ruby
170
+ class SendWelcomeEmail
171
+ include Medi8::NotificationHandler
172
+
173
+ subscribes_to UserRegistered, async: true
174
+
175
+ def call(event)
176
+ UserMailer.welcome_email(User.find(event.user_id)).deliver_later
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Publish
182
+
183
+ ```ruby
184
+ Medi8.publish(UserRegistered.new(user_id: 1))
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Advanced Usage
190
+
191
+ Medi8 is compatible with [Active CQRS](https://github.com/kiebor81/active_cqrs).
192
+
193
+ For project layout consistency, use `events` and `handlers/events` for your Medi8 classes. This will align nicely with the expected folder structures of Active CQRS. If following this advice, remember to auto-load these directories.
194
+
195
+ ```ruby
196
+ config.autoload_paths += %W[
197
+ #{config.root}/app/events
198
+ #{config.root}/app/handlers/events
199
+ ]
200
+
201
+ config.eager_load_paths += %W[
202
+ #{config.root}/app/events
203
+ #{config.root}/app/handlers/events
204
+ ]
205
+ ```
206
+
207
+ Alternatively, namespace appropriately if you prefer nested classes.
208
+
209
+ ### Mediator Pattern with CQRS
210
+
211
+ Example Active CQRS command:
212
+
213
+ ```ruby
214
+ # app/commands/create_user_command.rb
215
+ class CreateUserCommand
216
+ attr_reader :name, :email
217
+
218
+ def initialize(name:, email:)
219
+ @name = name
220
+ @email = email
221
+ end
222
+ end
223
+ ```
224
+
225
+ Active CQRS handler with Medi8:
226
+
227
+ ```ruby
228
+ # app/handlers/commands/create_user_handler.rb
229
+ class CreateUserHandler
230
+ def call(command)
231
+ user = User.create!(name: command.name, email: command.email)
232
+
233
+ Medi8.publish(UserRegistered.new(user_id: user.id, email: user.email))
234
+
235
+ user
236
+ end
237
+ end
238
+ ```
239
+
240
+ Medi8 notification published from the handler:
241
+
242
+ ```ruby
243
+ # app/events/user_registered.rb
244
+ class UserRegistered
245
+ attr_reader :user_id, :email
246
+
247
+ def initialize(user_id:, email:)
248
+ @user_id = user_id
249
+ @email = email
250
+ end
251
+ end
252
+ ```
253
+
254
+ Medi8 notification handler(s):
255
+
256
+ ```ruby
257
+ # app/handlers/events/send_welcome_email_handler.rb
258
+ class SendWelcomeEmailHandler
259
+ include Medi8::NotificationHandler
260
+ subscribes_to UserRegistered
261
+
262
+ def call(event)
263
+ UserMailer.welcome_email(event.email).deliver_later
264
+ end
265
+ end
266
+ ```
267
+
268
+ ```ruby
269
+ # app/handlers/events/track_user_registration_handler.rb
270
+ class TrackUserRegistrationHandler
271
+ include Medi8::NotificationHandler
272
+ subscribes_to UserRegistered
273
+
274
+ def call(event)
275
+ Rails.logger.info("=> User registered: #{event.user_id} (#{event.email})")
276
+ end
277
+ end
278
+ ```
279
+
280
+ Example controller:
281
+
282
+ ```ruby
283
+ class UsersController < ApplicationController
284
+ def create
285
+ command = CreateUserCommand.new(
286
+ name: params[:name],
287
+ email: params[:email]
288
+ )
289
+
290
+ user = CQRS_COMMAND_BUS.call(command)
291
+ render json: user, status: :created
292
+ rescue ActiveRecord::RecordInvalid => e
293
+ render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity
294
+ end
295
+ end
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Testing
301
+
302
+ ```bash
303
+ $ bundle exec rspec
304
+ ```
305
+
306
+ ### Mock E2E
307
+
308
+ Provided is an end-to-end mock flow under `mock_e2e`. Test this from the terminal.
309
+
310
+ ```bash
311
+ ruby mock_e2e.rb
312
+ ```
313
+
314
+ ---
315
+
316
+ ## License
317
+
318
+ MIT License © kiebor81
319
+
320
+ ---
321
+
322
+ ## Contributing
323
+
324
+ Bug reports and pull requests welcome.
325
+
326
+ 1. Fork it
327
+ 2. Create your feature branch (`git checkout -b my-feature`)
328
+ 3. Commit your changes (`git commit -am 'Add feature'`)
329
+ 4. Push to the branch (`git push origin my-feature`)
330
+ 5. Create a new Pull Request
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # Configuration class for Medi8, allowing users to set up middleware and registry.
5
+ class Configuration
6
+ # Initializes a new configuration instance with an empty registry and middleware stack.
7
+ def initialize
8
+ @registry = Medi8::Registry.new
9
+ @middleware_stack = Medi8::MiddlewareStack.new
10
+ end
11
+
12
+ attr_reader :registry, :middleware_stack
13
+
14
+ # Adds a new middleware registry entry.
15
+ def use(middleware)
16
+ @middleware_stack.use(middleware)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # The Handler module is used to define classes that can handle specific types of requests.
5
+ module Handler
6
+ # This method is called when the module is included in a class.
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ # This method is used to register a handler for a specific request class.
12
+ module ClassMethods
13
+ def handles(request_class)
14
+ Medi8.registry.register(request_class, self)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # This job is used to handle notifications in Medi8.
5
+ module Jobs
6
+ # NotificationJob is an ActiveJob that processes notifications.
7
+ class NotificationJob < ActiveJob::Base
8
+ queue_as :default
9
+
10
+ # Perform the job with the given handler class name, event hash, and event class name.
11
+ def perform(handler_class_name, event_hash, event_class_name)
12
+ handler = handler_class_name.constantize.new
13
+ event = event_class_name.constantize.new(**event_hash.symbolize_keys)
14
+ handler.call(event)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # Mediator is the main entry point for handling requests and publishing events.
5
+ class Mediator
6
+ def initialize(registry)
7
+ @registry = registry
8
+ end
9
+
10
+ # Sends a request to the appropriate handler.
11
+ def send(request)
12
+ handler_class = @registry.find_handler_for(request.class)
13
+ raise "No handler registered for #{request.class}" unless handler_class
14
+
15
+ final = -> { handler_class.new.call(request) }
16
+
17
+ if defined?(Medi8.middleware_stack)
18
+ Medi8.middleware_stack.call(request, &final)
19
+ else
20
+ final.call
21
+ end
22
+ end
23
+
24
+ # Publishes an event to all registered subscribers.
25
+ def publish(event)
26
+ NotificationDispatcher.new(@registry).publish(event)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # MiddlewareStack is a stack of middleware that can be used to process requests.
5
+ class MiddlewareStack
6
+ # Initializes a new MiddlewareStack.
7
+ def initialize
8
+ @middlewares = []
9
+ end
10
+
11
+ # Adds a middleware to the stack.
12
+ def use(middleware)
13
+ @middlewares << middleware
14
+ end
15
+
16
+ # Calls the middlewares in reverse order, passing the request and a final block.
17
+ def call(request, &final)
18
+ @middlewares.reverse.inject(final) do |next_middleware, middleware|
19
+ -> { middleware.new.call(request, &next_middleware) }
20
+ end.call
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # Medi8 Notifications Module
5
+ module NotificationHandler
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ # Registers a class as a notification handler for a specific event class.
11
+ module ClassMethods
12
+ def subscribes_to(event_class, async: false)
13
+ Medi8.registry.register_notification(event_class, self, async: async)
14
+ end
15
+ end
16
+ end
17
+
18
+ # Medi8 Notification dispatcher
19
+ class NotificationDispatcher
20
+ def initialize(registry)
21
+ @registry = registry
22
+ end
23
+
24
+ # Publishes an event to all registered handlers for that event class.
25
+ def publish(event) # rubocop:disable Metrics/MethodLength
26
+ handlers = @registry.find_notification_handlers_for(event.class)
27
+ handlers.each do |handler_def|
28
+ handler_class, async = handler_def
29
+
30
+ if async
31
+ Medi8::Jobs::NotificationJob.perform_later(
32
+ handler_class.name,
33
+ event.instance_variables.to_h { |var| [var.to_s.delete("@"), event.instance_variable_get(var)] },
34
+ event.class.name
35
+ )
36
+ else
37
+ handler_class.new.call(event)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # Railtie for integrating Medi8 with Rails
5
+ class Railtie < Rails::Railtie
6
+ initializer "medi8.load_handlers" do
7
+ ActiveSupport.on_load(:after_initialize) do
8
+ # Auto-load files like app/requests/** and app/handlers/**
9
+ Dir[Rails.root.join("app", "handlers", "**", "*.rb")].each { |file| require file }
10
+ Dir[Rails.root.join("app", "requests", "**", "*.rb")].each { |file| require file }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ # Registry for managing request handlers and event notifications
5
+ class Registry
6
+ def initialize
7
+ @handlers = {}
8
+ end
9
+
10
+ # Singleton instance of the Registry
11
+ def register(request_class, handler_class)
12
+ @handlers[request_class] = handler_class
13
+ end
14
+
15
+ # Find the handler for a given request class
16
+ def find_handler_for(request_class)
17
+ @handlers[request_class]
18
+ end
19
+
20
+ # Register a notification event and its handler
21
+ def register_notification(event_class, handler_class, async: false)
22
+ @notifications ||= Hash.new { |h, k| h[k] = [] }
23
+ @notifications[event_class] << [handler_class, async]
24
+ end
25
+
26
+ # Find all handlers for a given notification event class
27
+ def find_notification_handlers_for(event_class)
28
+ (@notifications && @notifications[event_class]) || []
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Medi8
4
+ VERSION = "0.1.0"
5
+ end
data/lib/medi8.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "medi8/version"
4
+ require_relative "medi8/registry"
5
+ require_relative "medi8/mediator"
6
+ require_relative "medi8/handler"
7
+ require_relative "medi8/railtie" if defined?(Rails)
8
+ require_relative "medi8/configuration"
9
+ require_relative "medi8/middleware_stack"
10
+ require_relative "medi8/notifications"
11
+
12
+ # Medi8 is a lightweight event-driven framework for Ruby applications.
13
+ module Medi8
14
+ class << self
15
+ # Configures the Medi8 framework.
16
+ def configure
17
+ yield configuration
18
+ end
19
+
20
+ # Returns the current configuration for Medi8.
21
+ def configuration
22
+ @configuration ||= Medi8::Configuration.new
23
+ end
24
+
25
+ # Provides a convenient way to access the Mediator instance.
26
+ def send(request)
27
+ Mediator.new(registry).send(request)
28
+ end
29
+
30
+ # Publishes an event to the Mediator, which will notify all registered handlers.
31
+ def publish(event)
32
+ Mediator.new(registry).publish(event)
33
+ end
34
+
35
+ # Returns the registry instance used by Medi8.
36
+ def registry
37
+ configuration.registry
38
+ end
39
+
40
+ # Returns the middleware stack used by Medi8.
41
+ def middleware_stack
42
+ configuration.middleware_stack
43
+ end
44
+
45
+ # Adds a middleware to the Medi8 middleware stack.
46
+ def use(middleware)
47
+ middleware_stack.use(middleware)
48
+ end
49
+ end
50
+ end
data/sig/medi8.rbs ADDED
@@ -0,0 +1,51 @@
1
+ # medi8.rbs
2
+ module Medi8
3
+ def self.configure: () { (Configuration) -> void } -> void
4
+ def self.registry: () -> Registry
5
+ def self.send: (untyped request) -> untyped
6
+ def self.publish: (untyped event) -> void
7
+ def self.middleware_stack: () -> MiddlewareStack
8
+ def self.use: (untyped middleware) -> void
9
+
10
+ class Configuration
11
+ def registry: () -> Registry
12
+ def middleware_stack: () -> MiddlewareStack
13
+ def use: (untyped middleware) -> void
14
+ end
15
+
16
+ class Registry
17
+ def register: (untyped request_class, untyped handler_class) -> void
18
+ def find_handler_for: (untyped request_class) -> untyped
19
+ def register_notification: (untyped event_class, untyped handler_class, ?async: bool) -> void
20
+ def find_notification_handlers_for: (untyped event_class) -> Array[[untyped, bool]]
21
+ end
22
+
23
+ class MiddlewareStack
24
+ def use: (untyped middleware) -> void
25
+ def call: (untyped request, &block) -> untyped
26
+ end
27
+
28
+ class Mediator
29
+ def initialize: (Registry registry) -> void
30
+ def send: (untyped request) -> untyped
31
+ def publish: (untyped event) -> void
32
+ end
33
+
34
+ module Handler
35
+ module ClassMethods
36
+ def handles: (untyped request_class) -> void
37
+ end
38
+ end
39
+
40
+ module NotificationHandler
41
+ module ClassMethods
42
+ def subscribes_to: (untyped event_class, ?async: bool) -> void
43
+ end
44
+ end
45
+
46
+ module Jobs
47
+ class NotificationJob < ::ActiveJob::Base
48
+ def perform: (String handler_class_name, Hash[String, untyped] event_hash, String event_class_name) -> void
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ # spec/medi8/configuration_spec.rb
2
+ RSpec.describe Medi8::Configuration do
3
+ it "exposes registry and middleware stack" do
4
+ config = described_class.new
5
+ expect(config.registry).to be_a(Medi8::Registry)
6
+ expect(config.middleware_stack).to be_a(Medi8::MiddlewareStack)
7
+ end
8
+
9
+ it "accepts middleware via use" do
10
+ mw = Class.new
11
+ config = described_class.new
12
+ config.use(mw)
13
+ expect(config.middleware_stack.instance_variable_get(:@middlewares)).to include(mw)
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ RSpec.describe Medi8::Handler do
2
+ it "registers a handler for a request class" do
3
+ request_class = Struct.new(:value, keyword_init: true)
4
+
5
+ handler_class = Class.new do
6
+ include Medi8::Handler
7
+
8
+ # The constant must be defined *inside* the test where the request_class is in scope
9
+ handles request_class
10
+
11
+ def call(req)
12
+ "Handled #{req.value}"
13
+ end
14
+ end
15
+
16
+ expect(Medi8.registry.find_handler_for(request_class)).to eq(handler_class)
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # spec/medi8/mediator_spec.rb
2
+ RSpec.describe Medi8::Mediator do
3
+ let(:registry) { Medi8::Registry.new }
4
+ let(:request_class) { Class.new { attr_reader :val; def initialize(val:); @val = val; end } }
5
+ let(:handler_class) { Class.new { def call(req); "got #{req.val}"; end } }
6
+
7
+ it "dispatches a request to its handler" do
8
+ registry.register(request_class, handler_class)
9
+ result = described_class.new(registry).send(request_class.new(val: "x"))
10
+ expect(result).to eq("got x")
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ # spec/medi8/middleware_stack_spec.rb
2
+ RSpec.describe Medi8::MiddlewareStack do
3
+ let(:stack) { described_class.new }
4
+
5
+ class TestMiddleware
6
+ def call(req)
7
+ req[:trace] << :middleware
8
+ yield
9
+ end
10
+ end
11
+
12
+ it "wraps execution in middleware layers" do
13
+ req = { trace: [] }
14
+ stack.use TestMiddleware
15
+ result = stack.call(req) { req[:trace] << :handler }
16
+ expect(req[:trace]).to eq([:middleware, :handler])
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ RSpec.describe Medi8::NotificationHandler do
2
+ it "registers and executes notification handlers" do
3
+ event_class = Struct.new(:data, keyword_init: true)
4
+
5
+ Class.new do
6
+ include Medi8::NotificationHandler
7
+ subscribes_to event_class
8
+
9
+ def call(event)
10
+ event.data << :called
11
+ end
12
+ end
13
+
14
+ data = []
15
+ event = event_class.new(data: data)
16
+ Medi8.publish(event)
17
+
18
+ expect(data).to include(:called)
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # spec/medi8/registry_spec.rb
2
+ RSpec.describe Medi8::Registry do
3
+ let(:registry) { described_class.new }
4
+ let(:request_class) { Class.new }
5
+ let(:handler_class) { Class.new }
6
+
7
+ it "registers and retrieves a handler" do
8
+ registry.register(request_class, handler_class)
9
+ expect(registry.find_handler_for(request_class)).to eq(handler_class)
10
+ end
11
+
12
+ it "registers and retrieves notification handlers" do
13
+ registry.register_notification(String, handler_class)
14
+ expect(registry.find_notification_handlers_for(String)).to include([handler_class, false])
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # spec/medi8_spec.rb
2
+ RSpec.describe Medi8 do
3
+ let(:request_class) { Class.new { attr_reader :value; def initialize(value:); @value = value; end } }
4
+ let(:handler_class) {
5
+ Class.new do
6
+ def call(req); "handled #{req.value}"; end
7
+ end
8
+ }
9
+
10
+ it "registers and sends a request to a handler" do
11
+ Medi8.registry.register(request_class, handler_class)
12
+ request = request_class.new(value: "test")
13
+ expect(Medi8.send(request)).to eq("handled test")
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: medi8-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kiebor81
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.12'
41
+ description: Medi8 is a lightweight, idiomatic mediator pattern implementation for
42
+ Ruby and Rails, inspired by MediatR (from .NET)
43
+ email:
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - sig/medi8.rbs
48
+ files:
49
+ - README.md
50
+ - lib/medi8.rb
51
+ - lib/medi8/configuration.rb
52
+ - lib/medi8/handler.rb
53
+ - lib/medi8/jobs/notification_job.rb
54
+ - lib/medi8/mediator.rb
55
+ - lib/medi8/middleware_stack.rb
56
+ - lib/medi8/notifications.rb
57
+ - lib/medi8/railtie.rb
58
+ - lib/medi8/registry.rb
59
+ - lib/medi8/version.rb
60
+ - sig/medi8.rbs
61
+ - spec/medi8/configuration_spec.rb
62
+ - spec/medi8/handler_spec.rb
63
+ - spec/medi8/mediator_spec.rb
64
+ - spec/medi8/middleware_stack_spec.rb
65
+ - spec/medi8/notifications_spec.rb
66
+ - spec/medi8/registry_spec.rb
67
+ - spec/medi8_spec.rb
68
+ homepage: https://github.com/kiebor81/medi8-rb
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.5.17
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Lightweight Ruby/Rails mediator inspired by MediatR
91
+ test_files:
92
+ - spec/medi8/configuration_spec.rb
93
+ - spec/medi8/handler_spec.rb
94
+ - spec/medi8/mediator_spec.rb
95
+ - spec/medi8/middleware_stack_spec.rb
96
+ - spec/medi8/notifications_spec.rb
97
+ - spec/medi8/registry_spec.rb
98
+ - spec/medi8_spec.rb