servus 0.1.4 → 0.1.6
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/.claude/commands/check-docs.md +1 -0
- data/.claude/commands/consistency-check.md +1 -0
- data/.claude/commands/fine-tooth-comb.md +1 -0
- data/.claude/commands/red-green-refactor.md +5 -0
- data/.claude/settings.json +15 -0
- data/.rubocop.yml +18 -2
- data/CHANGELOG.md +46 -0
- data/CLAUDE.md +10 -0
- data/IDEAS.md +1 -1
- data/READme.md +153 -5
- data/builds/servus-0.1.4.gem +0 -0
- data/builds/servus-0.1.5.gem +0 -0
- data/docs/core/2_architecture.md +32 -4
- data/docs/current_focus.md +569 -0
- data/docs/features/5_event_bus.md +244 -0
- data/docs/integration/1_configuration.md +60 -7
- data/docs/integration/2_testing.md +123 -0
- data/lib/generators/servus/event_handler/event_handler_generator.rb +59 -0
- data/lib/generators/servus/event_handler/templates/handler.rb.erb +86 -0
- data/lib/generators/servus/event_handler/templates/handler_spec.rb.erb +48 -0
- data/lib/generators/servus/service/service_generator.rb +4 -0
- data/lib/generators/servus/service/templates/arguments.json.erb +19 -10
- data/lib/generators/servus/service/templates/result.json.erb +8 -2
- data/lib/generators/servus/service/templates/service.rb.erb +101 -4
- data/lib/generators/servus/service/templates/service_spec.rb.erb +67 -6
- data/lib/servus/base.rb +21 -5
- data/lib/servus/config.rb +34 -14
- data/lib/servus/event_handler.rb +275 -0
- data/lib/servus/events/bus.rb +137 -0
- data/lib/servus/events/emitter.rb +162 -0
- data/lib/servus/events/errors.rb +10 -0
- data/lib/servus/railtie.rb +16 -0
- data/lib/servus/support/validator.rb +27 -0
- data/lib/servus/testing/matchers.rb +88 -0
- data/lib/servus/testing.rb +2 -0
- data/lib/servus/version.rb +1 -1
- data/lib/servus.rb +6 -0
- metadata +19 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Events
|
|
5
|
+
# Thread-safe event bus for registering and dispatching event handlers.
|
|
6
|
+
#
|
|
7
|
+
# The Bus acts as a central registry that maps event names to their
|
|
8
|
+
# corresponding handler classes. It uses ActiveSupport::Notifications
|
|
9
|
+
# internally to provide instrumentation and thread-safe event dispatch.
|
|
10
|
+
#
|
|
11
|
+
# Events are automatically instrumented and will appear in Rails logs
|
|
12
|
+
# with timing information, making it easy to monitor event performance.
|
|
13
|
+
#
|
|
14
|
+
# @example Registering a handler
|
|
15
|
+
# class UserCreatedHandler < Servus::EventHandler
|
|
16
|
+
# handles :user_created
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Servus::Events::Bus.register_handler(:user_created, UserCreatedHandler)
|
|
20
|
+
#
|
|
21
|
+
# @example Retrieving handlers for an event
|
|
22
|
+
# handlers = Servus::Events::Bus.handlers_for(:user_created)
|
|
23
|
+
# handlers.each { |handler| handler.handle(payload) }
|
|
24
|
+
#
|
|
25
|
+
# @example Instrumentation in logs
|
|
26
|
+
# Bus.emit(:user_created, user_id: 123)
|
|
27
|
+
# # Rails log: servus.events.user_created (1.2ms) {:user_id=>123}
|
|
28
|
+
#
|
|
29
|
+
# @see Servus::EventHandler
|
|
30
|
+
class Bus
|
|
31
|
+
class << self
|
|
32
|
+
# Registers a handler class for a specific event.
|
|
33
|
+
#
|
|
34
|
+
# Multiple handlers can be registered for the same event, and they
|
|
35
|
+
# will all be invoked when the event is emitted. The handler is
|
|
36
|
+
# automatically subscribed to ActiveSupport::Notifications.
|
|
37
|
+
#
|
|
38
|
+
# Handlers are typically registered automatically when EventHandler
|
|
39
|
+
# classes are loaded at boot time via the `handles` DSL method.
|
|
40
|
+
#
|
|
41
|
+
# @param event_name [Symbol] the name of the event
|
|
42
|
+
# @param handler_class [Class] the handler class to register
|
|
43
|
+
# @return [Array] the updated array of handlers for this event
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# Bus.register_handler(:user_created, UserCreatedHandler)
|
|
47
|
+
def register_handler(event_name, handler_class)
|
|
48
|
+
handlers[event_name] ||= []
|
|
49
|
+
handlers[event_name] << handler_class
|
|
50
|
+
|
|
51
|
+
# Subscribe to ActiveSupport::Notifications
|
|
52
|
+
subscription = ActiveSupport::Notifications.subscribe(notification_name(event_name)) do |*args|
|
|
53
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
54
|
+
handler_class.handle(event.payload)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Store subscription for cleanup
|
|
58
|
+
subscriptions[event_name] ||= []
|
|
59
|
+
subscriptions[event_name] << subscription
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Retrieves all registered handlers for a specific event.
|
|
63
|
+
#
|
|
64
|
+
# Returns a duplicate array to prevent external modification of the
|
|
65
|
+
# internal handler registry.
|
|
66
|
+
#
|
|
67
|
+
# @param event_name [Symbol] the name of the event
|
|
68
|
+
# @return [Array<Class>] array of handler classes registered for this event
|
|
69
|
+
#
|
|
70
|
+
# @example
|
|
71
|
+
# handlers = Bus.handlers_for(:user_created)
|
|
72
|
+
# handlers.each { |handler| handler.handle(payload) }
|
|
73
|
+
def handlers_for(event_name)
|
|
74
|
+
(handlers[event_name] || []).dup
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Emits an event to all registered handlers with instrumentation.
|
|
78
|
+
#
|
|
79
|
+
# Uses ActiveSupport::Notifications to instrument the event, providing
|
|
80
|
+
# automatic timing and logging. The event will appear in Rails logs
|
|
81
|
+
# with duration and payload information.
|
|
82
|
+
#
|
|
83
|
+
# @param event_name [Symbol] the name of the event to emit
|
|
84
|
+
# @param payload [Hash] the event payload to pass to handlers
|
|
85
|
+
# @return [void]
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# Bus.emit(:user_created, { user_id: 123, email: 'user@example.com' })
|
|
89
|
+
# # Rails log: servus.events.user_created (1.2ms) {:user_id=>123, :email=>"user@example.com"}
|
|
90
|
+
def emit(event_name, payload)
|
|
91
|
+
ActiveSupport::Notifications.instrument(notification_name(event_name), payload)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Clears all registered handlers and unsubscribes from notifications.
|
|
95
|
+
#
|
|
96
|
+
# Useful for testing and development mode reloading.
|
|
97
|
+
#
|
|
98
|
+
# @return [void]
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# Bus.clear
|
|
102
|
+
def clear
|
|
103
|
+
subscriptions.values.flatten.each do |subscription|
|
|
104
|
+
ActiveSupport::Notifications.unsubscribe(subscription)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@handlers = nil
|
|
108
|
+
@subscriptions = nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# Hash storing event handlers.
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] hash mapping event names to handler arrays
|
|
116
|
+
def handlers
|
|
117
|
+
@handlers ||= {}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Hash storing ActiveSupport::Notifications subscriptions.
|
|
121
|
+
#
|
|
122
|
+
# @return [Hash] hash mapping event names to subscription objects
|
|
123
|
+
def subscriptions
|
|
124
|
+
@subscriptions ||= {}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Converts an event name to a namespaced notification name.
|
|
128
|
+
#
|
|
129
|
+
# @param event_name [Symbol] the event name
|
|
130
|
+
# @return [String] the namespaced notification name
|
|
131
|
+
def notification_name(event_name)
|
|
132
|
+
"servus.events.#{event_name}"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Events
|
|
5
|
+
# Provides event emission DSL for service objects.
|
|
6
|
+
#
|
|
7
|
+
# This module adds the `emits` class method to services, allowing them to
|
|
8
|
+
# declare events that will be automatically emitted on success, failure, or error.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# class CreateUser < Servus::Base
|
|
12
|
+
# emits :user_created, on: :success
|
|
13
|
+
# emits :user_failed, on: :failure
|
|
14
|
+
# end
|
|
15
|
+
module Emitter
|
|
16
|
+
extend ActiveSupport::Concern
|
|
17
|
+
|
|
18
|
+
# Emits events for a service result.
|
|
19
|
+
#
|
|
20
|
+
# Called automatically after service execution completes. Determines the
|
|
21
|
+
# trigger type based on the result and emits all configured events.
|
|
22
|
+
#
|
|
23
|
+
# @param instance [Servus::Base] the service instance
|
|
24
|
+
# @param result [Servus::Support::Response] the service result
|
|
25
|
+
# @return [void]
|
|
26
|
+
# @api private
|
|
27
|
+
def self.emit_result_events!(instance, result)
|
|
28
|
+
trigger = result.success? ? :success : :failure
|
|
29
|
+
instance.send(:emit_events_for, trigger, result)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class_methods do
|
|
33
|
+
# Declares an event that this service will emit.
|
|
34
|
+
#
|
|
35
|
+
# Events are automatically emitted when the service completes with the specified
|
|
36
|
+
# trigger condition (:success, :failure, or :error). Use the `with` option to
|
|
37
|
+
# provide a custom payload builder, or pass a block.
|
|
38
|
+
#
|
|
39
|
+
# @param event_name [Symbol] the name of the event to emit
|
|
40
|
+
# @param on [Symbol] when to emit (:success, :failure, or :error)
|
|
41
|
+
# @param with [Symbol, nil] optional instance method name for building the payload
|
|
42
|
+
# @yield [result] optional block for building the payload
|
|
43
|
+
# @yieldparam result [Servus::Support::Response] the service result
|
|
44
|
+
# @yieldreturn [Hash] the event payload
|
|
45
|
+
# @return [void]
|
|
46
|
+
#
|
|
47
|
+
# @example Emit on success with default payload
|
|
48
|
+
# class CreateUser < Servus::Base
|
|
49
|
+
# emits :user_created, on: :success
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# @example Emit with custom payload builder method
|
|
53
|
+
# class CreateUser < Servus::Base
|
|
54
|
+
# emits :user_created, on: :success, with: :user_payload
|
|
55
|
+
#
|
|
56
|
+
# private
|
|
57
|
+
#
|
|
58
|
+
# def user_payload(result)
|
|
59
|
+
# { user_id: result.data[:user].id }
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# @example Emit with custom payload builder block
|
|
64
|
+
# class CreateUser < Servus::Base
|
|
65
|
+
# emits :user_created, on: :success do |result|
|
|
66
|
+
# { user_id: result.data[:user].id }
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# @note Best Practice: Services should typically emit ONE event per trigger
|
|
71
|
+
# that represents their core concern. Multiple downstream reactions should
|
|
72
|
+
# be coordinated by EventHandler classes, not by emitting multiple events
|
|
73
|
+
# from the service. This maintains separation of concerns.
|
|
74
|
+
#
|
|
75
|
+
# @example Recommended pattern (one event, multiple handlers)
|
|
76
|
+
# # Service emits one event
|
|
77
|
+
# class CreateUser < Servus::Base
|
|
78
|
+
# emits :user_created, on: :success
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# # Handler coordinates multiple reactions
|
|
82
|
+
# class UserCreatedHandler < Servus::EventHandler
|
|
83
|
+
# handles :user_created
|
|
84
|
+
# invoke SendWelcomeEmail::Service, async: true
|
|
85
|
+
# invoke TrackAnalytics::Service, async: true
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# @see Servus::Events::Bus
|
|
89
|
+
# @see Servus::EventHandler
|
|
90
|
+
def emits(event_name, on:, with: nil, &block)
|
|
91
|
+
valid_triggers = %i[success failure error!]
|
|
92
|
+
|
|
93
|
+
unless valid_triggers.include?(on)
|
|
94
|
+
raise ArgumentError, "Invalid trigger: #{on}. Must be one of: #{valid_triggers.join(', ')}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
@event_emissions ||= { success: [], failure: [], error!: [] }
|
|
98
|
+
@event_emissions[on] << {
|
|
99
|
+
event_name: event_name,
|
|
100
|
+
payload_builder: block || with
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns all event emissions declared for this service.
|
|
105
|
+
#
|
|
106
|
+
# @return [Hash] hash of event emissions grouped by trigger
|
|
107
|
+
# { success: [...], failure: [...], error!: [...] }
|
|
108
|
+
def event_emissions
|
|
109
|
+
@event_emissions || { success: [], failure: [], error!: [] }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns event emissions for a specific trigger.
|
|
113
|
+
#
|
|
114
|
+
# @param trigger [Symbol] the trigger type (:success, :failure, :error!)
|
|
115
|
+
# @return [Array<Hash>] array of event configurations for this trigger
|
|
116
|
+
def emissions_for(trigger)
|
|
117
|
+
event_emissions[trigger] || []
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Emits events for a specific trigger with the given result.
|
|
122
|
+
#
|
|
123
|
+
# @param trigger [Symbol] the trigger type (:success, :failure, :error!)
|
|
124
|
+
# @param result [Servus::Support::Response] the service result
|
|
125
|
+
# @return [void]
|
|
126
|
+
# @api private
|
|
127
|
+
def emit_events_for(trigger, result)
|
|
128
|
+
self.class.emissions_for(trigger).each do |emission|
|
|
129
|
+
payload = build_event_payload(emission, result)
|
|
130
|
+
Servus::Events::Bus.emit(emission[:event_name], payload)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Instance methods for emitting events during service execution
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
# Builds the event payload using the configured payload builder or defaults.
|
|
138
|
+
#
|
|
139
|
+
# @param emission [Hash] the emission configuration
|
|
140
|
+
# @param result [Servus::Support::Response] the service result
|
|
141
|
+
# @return [Hash] the event payload
|
|
142
|
+
# @api private
|
|
143
|
+
def build_event_payload(emission, result)
|
|
144
|
+
builder = emission[:payload_builder]
|
|
145
|
+
|
|
146
|
+
if builder.is_a?(Proc)
|
|
147
|
+
# Block-based payload builder
|
|
148
|
+
builder.call(result)
|
|
149
|
+
elsif builder.is_a?(Symbol)
|
|
150
|
+
# Method-based payload builder
|
|
151
|
+
send(builder, result)
|
|
152
|
+
elsif result.success?
|
|
153
|
+
# Default for success: return data
|
|
154
|
+
result.data
|
|
155
|
+
else
|
|
156
|
+
# Default for failure/error: return error
|
|
157
|
+
result.error
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Events
|
|
5
|
+
# Raised when an event handler subscribes to an event that no service emits.
|
|
6
|
+
#
|
|
7
|
+
# This helps catch typos in event names and orphaned handlers.
|
|
8
|
+
class OrphanedHandlerError < StandardError; end
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/servus/railtie.rb
CHANGED
|
@@ -18,5 +18,21 @@ module Servus
|
|
|
18
18
|
Servus::Base.extend Servus::Extensions::Async::Call
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
# Load event handlers and clear on reload
|
|
23
|
+
config.to_prepare do
|
|
24
|
+
Servus::Events::Bus.clear if Rails.env.development?
|
|
25
|
+
|
|
26
|
+
# Eager load all event handlers
|
|
27
|
+
events_path = Rails.root.join(Servus.config.events_dir)
|
|
28
|
+
Dir[File.join(events_path, '**/*_handler.rb')].each do |handler_file|
|
|
29
|
+
require_dependency handler_file
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# NOTE: Event validation is available but not run automatically due to load order issues.
|
|
34
|
+
# To validate handlers match emitted events, call manually:
|
|
35
|
+
# Servus::EventHandler.validate_all_handlers!
|
|
36
|
+
# Or create a rake task for CI validation.
|
|
21
37
|
end
|
|
22
38
|
end
|
|
@@ -90,6 +90,33 @@ module Servus
|
|
|
90
90
|
result
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Validates event payload against the handler's payload schema.
|
|
94
|
+
#
|
|
95
|
+
# @param handler_class [Class] the event handler class
|
|
96
|
+
# @param payload [Hash] the event payload to validate
|
|
97
|
+
# @return [Boolean] true if validation passes
|
|
98
|
+
# @raise [Servus::Support::Errors::ValidationError] if payload fails validation
|
|
99
|
+
#
|
|
100
|
+
#
|
|
101
|
+
# @example
|
|
102
|
+
# Validator.validate_event_payload!(MyEventHandler, { user_id: 123 })
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
def self.validate_event_payload!(handler_class, payload)
|
|
106
|
+
schema = handler_class.payload_schema
|
|
107
|
+
return true unless schema
|
|
108
|
+
|
|
109
|
+
serialized_payload = payload.as_json
|
|
110
|
+
validation_errors = JSON::Validator.fully_validate(schema, serialized_payload)
|
|
111
|
+
|
|
112
|
+
if validation_errors.any?
|
|
113
|
+
raise Servus::Support::Errors::ValidationError,
|
|
114
|
+
"Invalid payload for event :#{handler_class.event_name}: #{validation_errors.join(', ')}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
93
120
|
# Loads and caches a schema for a service.
|
|
94
121
|
#
|
|
95
122
|
# Implements a three-tier lookup strategy:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
|
4
|
+
require 'rspec/expectations'
|
|
5
|
+
|
|
6
|
+
module Servus
|
|
7
|
+
module Testing
|
|
8
|
+
# RSpec matchers for testing Servus services and events.
|
|
9
|
+
module Matchers
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Matcher for asserting event emission
|
|
15
|
+
RSpec::Matchers.define :emit_event do |handler_class_or_symbol|
|
|
16
|
+
supports_block_expectations
|
|
17
|
+
|
|
18
|
+
chain :with do |payload|
|
|
19
|
+
@expected_payload = payload
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
match do |block|
|
|
23
|
+
@captured_events = []
|
|
24
|
+
|
|
25
|
+
subscription = ActiveSupport::Notifications.subscribe(/^servus\.events\./) do |name, *_args, payload|
|
|
26
|
+
event_name = name.sub('servus.events.', '').to_sym
|
|
27
|
+
@captured_events << { name: event_name, payload: payload }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
block.call
|
|
31
|
+
|
|
32
|
+
# Determine event name
|
|
33
|
+
@event_name = if handler_class_or_symbol.is_a?(Symbol)
|
|
34
|
+
handler_class_or_symbol
|
|
35
|
+
else
|
|
36
|
+
handler_class_or_symbol.event_name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@matching_event = @captured_events.find { |e| e[:name] == @event_name }
|
|
40
|
+
|
|
41
|
+
return false unless @matching_event
|
|
42
|
+
return true unless @expected_payload
|
|
43
|
+
|
|
44
|
+
RSpec::Matchers::BuiltIn::Match.new(@expected_payload).matches?(@matching_event[:payload])
|
|
45
|
+
ensure
|
|
46
|
+
ActiveSupport::Notifications.unsubscribe(subscription) if subscription
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
failure_message do
|
|
50
|
+
if @matching_event.nil?
|
|
51
|
+
"expected event :#{@event_name} to be emitted, but it was not.\n" \
|
|
52
|
+
"Emitted: #{@captured_events.map { |e| e[:name] }}"
|
|
53
|
+
else
|
|
54
|
+
"expected event :#{@event_name} payload to match #{@expected_payload.inspect}, " \
|
|
55
|
+
"got: #{@matching_event[:payload].inspect}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Matcher for asserting service invocation
|
|
61
|
+
RSpec::Matchers.define :call_service do |service_class|
|
|
62
|
+
supports_block_expectations
|
|
63
|
+
|
|
64
|
+
chain :with do |args|
|
|
65
|
+
@expected_args = args
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
chain :async do
|
|
69
|
+
@expect_async = true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
match do |block|
|
|
73
|
+
method_name = @expect_async ? :call_async : :call
|
|
74
|
+
|
|
75
|
+
expectation = expect(service_class).to receive(method_name)
|
|
76
|
+
expectation.with(@expected_args) if @expected_args
|
|
77
|
+
|
|
78
|
+
block.call
|
|
79
|
+
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
failure_message do
|
|
84
|
+
method = @expect_async ? 'call_async' : 'call'
|
|
85
|
+
"expected #{service_class} to receive #{method}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
# rubocop:enable Metrics/BlockLength
|
data/lib/servus/testing.rb
CHANGED
|
@@ -9,9 +9,11 @@ module Servus
|
|
|
9
9
|
#
|
|
10
10
|
# @see Servus::Testing::ExampleBuilders
|
|
11
11
|
# @see Servus::Testing::ExampleExtractor
|
|
12
|
+
# @see Servus::Testing::Matchers
|
|
12
13
|
module Testing
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
require_relative 'testing/example_extractor'
|
|
17
18
|
require_relative 'testing/example_builders'
|
|
19
|
+
require_relative 'testing/matchers'
|
data/lib/servus/version.rb
CHANGED
data/lib/servus.rb
CHANGED
|
@@ -25,6 +25,12 @@ require_relative 'servus/support/validator'
|
|
|
25
25
|
require_relative 'servus/support/errors'
|
|
26
26
|
require_relative 'servus/support/rescuer'
|
|
27
27
|
|
|
28
|
+
# Events
|
|
29
|
+
require_relative 'servus/events/errors'
|
|
30
|
+
require_relative 'servus/events/bus'
|
|
31
|
+
require_relative 'servus/events/emitter'
|
|
32
|
+
require_relative 'servus/event_handler'
|
|
33
|
+
|
|
28
34
|
# Core
|
|
29
35
|
require_relative 'servus/version'
|
|
30
36
|
require_relative 'servus/base'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: servus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Scholl
|
|
@@ -74,10 +74,16 @@ executables: []
|
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
|
+
- ".claude/commands/check-docs.md"
|
|
78
|
+
- ".claude/commands/consistency-check.md"
|
|
79
|
+
- ".claude/commands/fine-tooth-comb.md"
|
|
80
|
+
- ".claude/commands/red-green-refactor.md"
|
|
81
|
+
- ".claude/settings.json"
|
|
77
82
|
- ".rspec"
|
|
78
83
|
- ".rubocop.yml"
|
|
79
84
|
- ".yardopts"
|
|
80
85
|
- CHANGELOG.md
|
|
86
|
+
- CLAUDE.md
|
|
81
87
|
- IDEAS.md
|
|
82
88
|
- LICENSE.txt
|
|
83
89
|
- READme.md
|
|
@@ -87,13 +93,16 @@ files:
|
|
|
87
93
|
- builds/servus-0.1.2.gem
|
|
88
94
|
- builds/servus-0.1.3.gem
|
|
89
95
|
- builds/servus-0.1.4.gem
|
|
96
|
+
- builds/servus-0.1.5.gem
|
|
90
97
|
- docs/core/1_overview.md
|
|
91
98
|
- docs/core/2_architecture.md
|
|
92
99
|
- docs/core/3_service_objects.md
|
|
100
|
+
- docs/current_focus.md
|
|
93
101
|
- docs/features/1_schema_validation.md
|
|
94
102
|
- docs/features/2_error_handling.md
|
|
95
103
|
- docs/features/3_async_execution.md
|
|
96
104
|
- docs/features/4_logging.md
|
|
105
|
+
- docs/features/5_event_bus.md
|
|
97
106
|
- docs/guides/1_common_patterns.md
|
|
98
107
|
- docs/guides/2_migration_guide.md
|
|
99
108
|
- docs/integration/1_configuration.md
|
|
@@ -177,6 +186,9 @@ files:
|
|
|
177
186
|
- docs/yard/js/jquery.js
|
|
178
187
|
- docs/yard/method_list.html
|
|
179
188
|
- docs/yard/top-level-namespace.html
|
|
189
|
+
- lib/generators/servus/event_handler/event_handler_generator.rb
|
|
190
|
+
- lib/generators/servus/event_handler/templates/handler.rb.erb
|
|
191
|
+
- lib/generators/servus/event_handler/templates/handler_spec.rb.erb
|
|
180
192
|
- lib/generators/servus/service/service_generator.rb
|
|
181
193
|
- lib/generators/servus/service/templates/arguments.json.erb
|
|
182
194
|
- lib/generators/servus/service/templates/result.json.erb
|
|
@@ -185,6 +197,10 @@ files:
|
|
|
185
197
|
- lib/servus.rb
|
|
186
198
|
- lib/servus/base.rb
|
|
187
199
|
- lib/servus/config.rb
|
|
200
|
+
- lib/servus/event_handler.rb
|
|
201
|
+
- lib/servus/events/bus.rb
|
|
202
|
+
- lib/servus/events/emitter.rb
|
|
203
|
+
- lib/servus/events/errors.rb
|
|
188
204
|
- lib/servus/extensions/async/call.rb
|
|
189
205
|
- lib/servus/extensions/async/errors.rb
|
|
190
206
|
- lib/servus/extensions/async/ext.rb
|
|
@@ -199,6 +215,7 @@ files:
|
|
|
199
215
|
- lib/servus/testing.rb
|
|
200
216
|
- lib/servus/testing/example_builders.rb
|
|
201
217
|
- lib/servus/testing/example_extractor.rb
|
|
218
|
+
- lib/servus/testing/matchers.rb
|
|
202
219
|
- lib/servus/version.rb
|
|
203
220
|
- sig/servus.rbs
|
|
204
221
|
homepage: https://github.com/zarpay/servus
|
|
@@ -208,6 +225,7 @@ metadata:
|
|
|
208
225
|
allowed_push_host: https://rubygems.org
|
|
209
226
|
source_code_uri: https://github.com/zarpay/servus
|
|
210
227
|
changelog_uri: https://github.com/zarpay/servus/blob/main/CHANGELOG.md
|
|
228
|
+
rubygems_mfa_required: 'true'
|
|
211
229
|
rdoc_options: []
|
|
212
230
|
require_paths:
|
|
213
231
|
- lib
|