servus 0.1.1 → 0.1.2
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/CHANGELOG.md +4 -0
- data/READme.md +34 -11
- data/builds/servus-0.1.1.gem +0 -0
- data/lib/servus/config.rb +14 -5
- data/lib/servus/extensions/async/call.rb +47 -0
- data/lib/servus/extensions/async/errors.rb +18 -0
- data/lib/servus/extensions/async/ext.rb +15 -0
- data/lib/servus/extensions/async/job.rb +36 -0
- data/lib/servus/railtie.rb +8 -1
- data/lib/servus/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bddb5c486e4996dff5183d6a9bd57ef086739d7007a12125479d320464a0018
|
|
4
|
+
data.tar.gz: 2d948b6202d0888664a75766cf4e9a3c607b4f819e7d9293ac0d0ff8f42c6c32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18a6ffd8d72e5a452bda53b5d91a621d0b963872c08d72b8ee4a533160babfde16ed9690a1f9a0b2c5aa1a4c330f3e6209b9ada2ffdba3635bad9cf0bd351d8e
|
|
7
|
+
data.tar.gz: 59b87da3ae33ec0a586275892632b6fe84810b44d75759d9c6c2733fdb9224093d1ed9740467438908eb4a5640b41b6468f1c8381c65cc07adf843e23979fb80
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.2] - 2025-10-10
|
|
4
|
+
- Added: Added `call_async` method to `Servus::Base` to enqueue a job for calling the service asynchronously
|
|
5
|
+
- Added: Added `Async::Job` to handle async enqueing with support for ActiveJob set options
|
|
6
|
+
|
|
3
7
|
## [0.1.1] - 2025-08-20
|
|
4
8
|
|
|
5
9
|
- Added: Added `rescue_from` method to `Servus::Base` to rescue from standard errors and use custom error types.
|
data/READme.md
CHANGED
|
@@ -119,6 +119,29 @@ end
|
|
|
119
119
|
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
Here’s a section you can add to your README for the new `.call_async` feature, matching the style of your existing `## Inheritance` section:
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## **Asynchronous Execution**
|
|
127
|
+
|
|
128
|
+
You can asynchronously execute any service class that inherits from `Servus::Base` using `.call_async`. This uses `ActiveJob` under the hood and supports standard job options (`wait`, `queue`, `priority`, etc.). Only available in environments where `ActiveJob` is loaded (e.g., Rails apps)
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# Good ✅
|
|
132
|
+
Services::NotifyUser::Service.call_async(
|
|
133
|
+
user_id: current_user.id,
|
|
134
|
+
wait: 5.minutes,
|
|
135
|
+
queue: :low_priority,
|
|
136
|
+
job_options: { tags: ['notifications'] }
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Bad ❌
|
|
140
|
+
Services::NotifyUser::Support::MessageBuilder.call_async(
|
|
141
|
+
# Invalid: support classes don't inherit from Servus::Base
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
122
145
|
## **Inheritance**
|
|
123
146
|
|
|
124
147
|
- Every main service class (`service.rb`) must inherit from `Servus::Base`
|
|
@@ -249,12 +272,12 @@ end
|
|
|
249
272
|
class SomeController < AppController
|
|
250
273
|
def controller_action
|
|
251
274
|
result = SomeServiceObject::Service.call(arg: 1)
|
|
252
|
-
|
|
275
|
+
|
|
253
276
|
return if result.success?
|
|
254
|
-
|
|
277
|
+
|
|
255
278
|
# If you just want the error message
|
|
256
279
|
bad_request(result.error.message)
|
|
257
|
-
|
|
280
|
+
|
|
258
281
|
# If you want the API error
|
|
259
282
|
service_object_error(result.error.api_error)
|
|
260
283
|
end
|
|
@@ -271,9 +294,9 @@ class SomeServiceObject::Service < Servus::Base
|
|
|
271
294
|
class SomethingGlitched < StandardError; end
|
|
272
295
|
|
|
273
296
|
# Rescue from standard errors and use custom error
|
|
274
|
-
rescue_from
|
|
275
|
-
SomethingBroke,
|
|
276
|
-
SomethingGlitched,
|
|
297
|
+
rescue_from
|
|
298
|
+
SomethingBroke,
|
|
299
|
+
SomethingGlitched,
|
|
277
300
|
use: Servus::Support::Errors::ServiceUnavailableError # this is optional
|
|
278
301
|
|
|
279
302
|
def call
|
|
@@ -312,8 +335,8 @@ Service objects can be called from controllers using the `run_service` and `rend
|
|
|
312
335
|
|
|
313
336
|
### run_service
|
|
314
337
|
|
|
315
|
-
`run_service` calls the service object with the provided parameters and set's an instance variable `@result` to the
|
|
316
|
-
result of the service object if the result is successful. If the result is not successful, it will pass the result
|
|
338
|
+
`run_service` calls the service object with the provided parameters and set's an instance variable `@result` to the
|
|
339
|
+
result of the service object if the result is successful. If the result is not successful, it will pass the result
|
|
317
340
|
to error to the `render_service_object_error` helper. This allows for easy error handling in the controller for
|
|
318
341
|
repetetive usecases.
|
|
319
342
|
|
|
@@ -335,7 +358,7 @@ end
|
|
|
335
358
|
|
|
336
359
|
### render_service_object_error
|
|
337
360
|
|
|
338
|
-
`render_service_object_error` renders the error of a service object. It expects a hash with a `message` key and a `code` key from
|
|
361
|
+
`render_service_object_error` renders the error of a service object. It expects a hash with a `message` key and a `code` key from
|
|
339
362
|
the api_error method of the service error. This is all setup by default for a JSON API response, thought the method can be
|
|
340
363
|
overridden if needed to handle different usecases.
|
|
341
364
|
|
|
@@ -344,7 +367,7 @@ overridden if needed to handle different usecases.
|
|
|
344
367
|
#
|
|
345
368
|
# error = result.error.api_error
|
|
346
369
|
# => { message: "Error message", code: 400 }
|
|
347
|
-
#
|
|
370
|
+
#
|
|
348
371
|
# render json: { message: error[:message], code: error[:code] }, status: error[:code]
|
|
349
372
|
|
|
350
373
|
class SomeController < AppController
|
|
@@ -472,4 +495,4 @@ Both file-based and inline schemas are automatically cached:
|
|
|
472
495
|
|
|
473
496
|
- First validation request loads and caches the schema
|
|
474
497
|
- Subsequent validations use the cached version
|
|
475
|
-
- Cache can be cleared using `SchemaValidation.clear_cache!`
|
|
498
|
+
- Cache can be cleared using `SchemaValidation.clear_cache!`
|
|
Binary file
|
data/lib/servus/config.rb
CHANGED
|
@@ -9,11 +9,7 @@ module Servus
|
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
# Default to Rails.root if available, otherwise use current working directory
|
|
12
|
-
@schema_root =
|
|
13
|
-
Rails.root.join('app/schemas/services')
|
|
14
|
-
else
|
|
15
|
-
File.expand_path('../../../app/schemas/services', __dir__)
|
|
16
|
-
end
|
|
12
|
+
@schema_root = File.join(root_path, 'app/schemas/services')
|
|
17
13
|
end
|
|
18
14
|
|
|
19
15
|
# Returns the path for a specific service's schema
|
|
@@ -32,6 +28,19 @@ module Servus
|
|
|
32
28
|
def schema_dir_for(service_namespace)
|
|
33
29
|
File.join(schema_root.to_s, service_namespace)
|
|
34
30
|
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Sets the schema root directory
|
|
35
|
+
#
|
|
36
|
+
# @param path [String] the new schema root directory
|
|
37
|
+
def root_path
|
|
38
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
|
39
|
+
Rails.root
|
|
40
|
+
else
|
|
41
|
+
File.expand_path('../../..', __dir__)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
# Singleton config instance
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Extensions
|
|
5
|
+
module Async
|
|
6
|
+
# Calls the service asynchronously using AsyncCallerJob.
|
|
7
|
+
#
|
|
8
|
+
# Supports all standard ActiveJob scheduling and routing options:
|
|
9
|
+
# - wait: <ActiveSupport::Duration> (e.g., 5.minutes)
|
|
10
|
+
# - wait_until: <Time> (e.g., 2.hours.from_now)
|
|
11
|
+
# - queue: <Symbol/String> (e.g., :critical, 'low_priority')
|
|
12
|
+
# - priority: <Integer> (depends on adapter support)
|
|
13
|
+
# - retry: <Boolean> (custom control for job retry)
|
|
14
|
+
# - job_options: <Hash> (extra options, merged in)
|
|
15
|
+
#
|
|
16
|
+
# Example:
|
|
17
|
+
# call_async(
|
|
18
|
+
# wait: 10.minutes,
|
|
19
|
+
# queue: :low_priority,
|
|
20
|
+
# priority: 20,
|
|
21
|
+
# job_options: { tags: ['user_graduation'] },
|
|
22
|
+
# user_id: current_user.id
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
module Call
|
|
26
|
+
# @param args [Hash] The arguments to pass to the service and job options.
|
|
27
|
+
# @return [void]
|
|
28
|
+
def call_async(**args)
|
|
29
|
+
# Extract ActiveJob configuration options
|
|
30
|
+
job_options = args.slice(:wait, :wait_until, :queue, :priority)
|
|
31
|
+
job_options.merge!(args.delete(:job_options) || {}) # merge custom job options
|
|
32
|
+
|
|
33
|
+
# Remove special keys that shouldn't be passed to the service
|
|
34
|
+
args.except!(:wait, :wait_until, :queue, :priority, :job_options)
|
|
35
|
+
|
|
36
|
+
# Build job with optional delay, scheduling, or queue settings
|
|
37
|
+
job = job_options.any? ? Job.set(**job_options.compact) : Job
|
|
38
|
+
|
|
39
|
+
# Enqueue the job asynchronously
|
|
40
|
+
job.perform_later(name: name, args: args)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
raise Errors::JobEnqueueError, "Failed to enqueue async job for #{self}: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Extensions
|
|
5
|
+
module Async
|
|
6
|
+
module Errors
|
|
7
|
+
# Base error class for async extensions
|
|
8
|
+
class AsyncError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Error raised when the job fails to enqueue
|
|
11
|
+
class JobEnqueueError < AsyncError; end
|
|
12
|
+
|
|
13
|
+
# Error raised when the service class cannot be found
|
|
14
|
+
class ServiceNotFoundError < AsyncError; end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Extensions
|
|
5
|
+
# Async extensions for Servus
|
|
6
|
+
module Async
|
|
7
|
+
require 'servus/extensions/async/errors'
|
|
8
|
+
require 'servus/extensions/async/job'
|
|
9
|
+
require 'servus/extensions/async/call'
|
|
10
|
+
|
|
11
|
+
# Module providing async extensions for Servus
|
|
12
|
+
module Ext; end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Servus
|
|
4
|
+
module Extensions
|
|
5
|
+
module Async
|
|
6
|
+
# Job to run a service class with given arguments.
|
|
7
|
+
#
|
|
8
|
+
# This job will be migrated to Servus once it's stable as a .call_async method.
|
|
9
|
+
# It takes the fully-qualified class name of the service as a string and any keyword arguments
|
|
10
|
+
# required by the service's .call method.
|
|
11
|
+
#
|
|
12
|
+
# Example usage:
|
|
13
|
+
# RunServiceJob.perform_later('SomeModule::SomeService', arg1: value1, arg2: value2)
|
|
14
|
+
#
|
|
15
|
+
# This will invoke SomeModule::SomeService.call(arg1: value1, arg2: value2) in a background job.
|
|
16
|
+
#
|
|
17
|
+
# Errors during service execution are logged.
|
|
18
|
+
class Job < ActiveJob::Base
|
|
19
|
+
queue_as :default
|
|
20
|
+
|
|
21
|
+
def perform(name:, args:)
|
|
22
|
+
constantize!(name).call(**args)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :klass
|
|
28
|
+
|
|
29
|
+
def constantize!(class_name)
|
|
30
|
+
class_name.safe_constantize || (raise Errors::ServiceNotFoundError,
|
|
31
|
+
"Service class '#{class_name}' not found.")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/servus/railtie.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# lib/servus/railtie.rb
|
|
4
3
|
require 'rails/railtie'
|
|
5
4
|
|
|
6
5
|
module Servus
|
|
@@ -11,5 +10,13 @@ module Servus
|
|
|
11
10
|
include Servus::Helpers::ControllerHelpers
|
|
12
11
|
end
|
|
13
12
|
end
|
|
13
|
+
|
|
14
|
+
initializer 'servus.job_async' do
|
|
15
|
+
ActiveSupport.on_load(:active_job) do
|
|
16
|
+
require 'servus/extensions/async/ext'
|
|
17
|
+
# Extend the base service with the async call method
|
|
18
|
+
Servus::Base.extend Servus::Extensions::Async::Call
|
|
19
|
+
end
|
|
20
|
+
end
|
|
14
21
|
end
|
|
15
22
|
end
|
data/lib/servus/version.rb
CHANGED
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Scholl
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- READme.md
|
|
80
80
|
- Rakefile
|
|
81
81
|
- builds/servus-0.0.1.gem
|
|
82
|
+
- builds/servus-0.1.1.gem
|
|
82
83
|
- lib/generators/servus/service/service_generator.rb
|
|
83
84
|
- lib/generators/servus/service/templates/arguments.json.erb
|
|
84
85
|
- lib/generators/servus/service/templates/result.json.erb
|
|
@@ -87,6 +88,10 @@ files:
|
|
|
87
88
|
- lib/servus.rb
|
|
88
89
|
- lib/servus/base.rb
|
|
89
90
|
- lib/servus/config.rb
|
|
91
|
+
- lib/servus/extensions/async/call.rb
|
|
92
|
+
- lib/servus/extensions/async/errors.rb
|
|
93
|
+
- lib/servus/extensions/async/ext.rb
|
|
94
|
+
- lib/servus/extensions/async/job.rb
|
|
90
95
|
- lib/servus/helpers/controller_helpers.rb
|
|
91
96
|
- lib/servus/railtie.rb
|
|
92
97
|
- lib/servus/support/errors.rb
|