light-services 2.2 → 3.0.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 +4 -4
- data/.github/config/rubocop_linter_action.yml +4 -4
- data/.github/workflows/ci.yml +12 -12
- data/.gitignore +5 -0
- data/.rubocop.yml +77 -7
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +16 -11
- data/Gemfile.lock +53 -27
- data/README.md +76 -13
- data/docs/arguments.md +267 -0
- data/docs/best-practices.md +153 -0
- data/docs/callbacks.md +476 -0
- data/docs/concepts.md +80 -0
- data/docs/configuration.md +168 -0
- data/docs/context.md +128 -0
- data/docs/crud.md +525 -0
- data/docs/errors.md +250 -0
- data/docs/generators.md +250 -0
- data/docs/outputs.md +135 -0
- data/docs/pundit-authorization.md +320 -0
- data/docs/quickstart.md +134 -0
- data/docs/readme.md +100 -0
- data/docs/recipes.md +14 -0
- data/docs/service-rendering.md +222 -0
- data/docs/steps.md +337 -0
- data/docs/summary.md +19 -0
- data/docs/testing.md +549 -0
- data/lib/generators/light_services/install/USAGE +15 -0
- data/lib/generators/light_services/install/install_generator.rb +41 -0
- data/lib/generators/light_services/install/templates/application_service.rb.tt +8 -0
- data/lib/generators/light_services/install/templates/application_service_spec.rb.tt +7 -0
- data/lib/generators/light_services/install/templates/initializer.rb.tt +30 -0
- data/lib/generators/light_services/service/USAGE +21 -0
- data/lib/generators/light_services/service/service_generator.rb +68 -0
- data/lib/generators/light_services/service/templates/service.rb.tt +48 -0
- data/lib/generators/light_services/service/templates/service_spec.rb.tt +40 -0
- data/lib/light/services/base.rb +24 -114
- data/lib/light/services/base_with_context.rb +2 -3
- data/lib/light/services/callbacks.rb +103 -0
- data/lib/light/services/collection.rb +97 -0
- data/lib/light/services/concerns/execution.rb +76 -0
- data/lib/light/services/concerns/parent_service.rb +34 -0
- data/lib/light/services/concerns/state_management.rb +30 -0
- data/lib/light/services/config.rb +4 -18
- data/lib/light/services/constants.rb +97 -0
- data/lib/light/services/dsl/arguments_dsl.rb +84 -0
- data/lib/light/services/dsl/outputs_dsl.rb +80 -0
- data/lib/light/services/dsl/steps_dsl.rb +205 -0
- data/lib/light/services/dsl/validation.rb +132 -0
- data/lib/light/services/exceptions.rb +7 -2
- data/lib/light/services/messages.rb +19 -31
- data/lib/light/services/rspec/matchers/define_argument.rb +174 -0
- data/lib/light/services/rspec/matchers/define_output.rb +147 -0
- data/lib/light/services/rspec/matchers/define_step.rb +225 -0
- data/lib/light/services/rspec/matchers/execute_step.rb +230 -0
- data/lib/light/services/rspec/matchers/have_error_on.rb +148 -0
- data/lib/light/services/rspec/matchers/have_warning_on.rb +148 -0
- data/lib/light/services/rspec/matchers/trigger_callback.rb +138 -0
- data/lib/light/services/rspec.rb +15 -0
- data/lib/light/services/settings/field.rb +86 -0
- data/lib/light/services/settings/step.rb +31 -16
- data/lib/light/services/utils.rb +38 -0
- data/lib/light/services/version.rb +1 -1
- data/lib/light/services.rb +2 -0
- data/light-services.gemspec +6 -8
- metadata +54 -26
- data/lib/light/services/class_based_collection/base.rb +0 -86
- data/lib/light/services/class_based_collection/mount.rb +0 -33
- data/lib/light/services/collection/arguments.rb +0 -34
- data/lib/light/services/collection/base.rb +0 -59
- data/lib/light/services/collection/outputs.rb +0 -16
- data/lib/light/services/settings/argument.rb +0 -68
- data/lib/light/services/settings/output.rb +0 -34
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Service Rendering
|
|
2
|
+
|
|
3
|
+
This recipe provides a clean way to render service results and errors in your Rails controllers, reducing boilerplate and ensuring consistent API responses.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Without a helper, controller actions become repetitive:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class PostsController < ApplicationController
|
|
11
|
+
def create
|
|
12
|
+
service = Post::Create.run(service_args(attributes: params[:post]))
|
|
13
|
+
|
|
14
|
+
if service.success?
|
|
15
|
+
render json: service.post, status: :created
|
|
16
|
+
else
|
|
17
|
+
render json: { errors: service.errors.to_h }, status: :unprocessable_entity
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update
|
|
22
|
+
service = Post::Update.run(service_args(record: @post, attributes: params[:post]))
|
|
23
|
+
|
|
24
|
+
if service.success?
|
|
25
|
+
render json: service.post
|
|
26
|
+
else
|
|
27
|
+
render json: { errors: service.errors.to_h }, status: :unprocessable_entity
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ... same pattern repeated for every action
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The Solution
|
|
36
|
+
|
|
37
|
+
Create a `render_service` helper that handles success and failure automatically.
|
|
38
|
+
|
|
39
|
+
## Implementation
|
|
40
|
+
|
|
41
|
+
### Basic Helper
|
|
42
|
+
|
|
43
|
+
Add this to your `ApplicationController`:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
class ApplicationController < ActionController::API
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def render_service(service, success_status: :ok, error_status: :unprocessable_entity)
|
|
50
|
+
if service.success?
|
|
51
|
+
yield(service) if block_given?
|
|
52
|
+
render json: service_response(service), status: success_status
|
|
53
|
+
else
|
|
54
|
+
render json: { errors: service.errors.to_h }, status: error_status
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def service_response(service)
|
|
59
|
+
# Returns the first output that is set
|
|
60
|
+
service.class.outputs.each do |name, _|
|
|
61
|
+
value = service.public_send(name)
|
|
62
|
+
return value unless value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
{}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Usage
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
class PostsController < ApplicationController
|
|
74
|
+
def create
|
|
75
|
+
render_service Post::Create.run(service_args(attributes: params[:post])),
|
|
76
|
+
success_status: :created
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def update
|
|
80
|
+
render_service Post::Update.run(service_args(record: @post))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def destroy
|
|
84
|
+
render_service Post::Destroy.run(service_args(record: @post))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Advanced Implementation
|
|
90
|
+
|
|
91
|
+
### With Custom Response Building
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
class ApplicationController < ActionController::API
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def render_service(service, **options)
|
|
98
|
+
if service.success?
|
|
99
|
+
render_service_success(service, options)
|
|
100
|
+
else
|
|
101
|
+
render_service_failure(service, options)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def render_service_success(service, options)
|
|
106
|
+
status = options[:success_status] || :ok
|
|
107
|
+
|
|
108
|
+
response = if options[:response]
|
|
109
|
+
options[:response]
|
|
110
|
+
elsif options[:output]
|
|
111
|
+
service.public_send(options[:output])
|
|
112
|
+
else
|
|
113
|
+
auto_detect_response(service)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
render json: response, status: status
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def render_service_failure(service, options)
|
|
120
|
+
status = options[:error_status] || :unprocessable_entity
|
|
121
|
+
|
|
122
|
+
render json: {
|
|
123
|
+
errors: service.errors.to_h,
|
|
124
|
+
warnings: service.warnings.to_h
|
|
125
|
+
}.compact, status: status
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def auto_detect_response(service)
|
|
129
|
+
service.class.outputs.each do |name, _|
|
|
130
|
+
value = service.public_send(name)
|
|
131
|
+
return value unless value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
{ success: true }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Usage with Options
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class PostsController < ApplicationController
|
|
143
|
+
def create
|
|
144
|
+
service = Post::Create.run(service_args(attributes: params[:post]))
|
|
145
|
+
|
|
146
|
+
render_service service,
|
|
147
|
+
success_status: :created,
|
|
148
|
+
output: :post
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def bulk_create
|
|
152
|
+
service = Post::BulkCreate.run(service_args(items: params[:posts]))
|
|
153
|
+
|
|
154
|
+
render_service service,
|
|
155
|
+
success_status: :created,
|
|
156
|
+
response: { posts: service.posts, count: service.posts.count }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## With Serializers
|
|
162
|
+
|
|
163
|
+
If you're using a serializer library (like Alba, Blueprinter, or ActiveModel::Serializers):
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
class ApplicationController < ActionController::API
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def render_service(service, serializer: nil, **options)
|
|
170
|
+
if service.success?
|
|
171
|
+
response = auto_detect_response(service)
|
|
172
|
+
response = serializer.new(response).to_h if serializer && response
|
|
173
|
+
|
|
174
|
+
render json: response, status: options[:success_status] || :ok
|
|
175
|
+
else
|
|
176
|
+
render json: { errors: service.errors.to_h },
|
|
177
|
+
status: options[:error_status] || :unprocessable_entity
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
class PostsController < ApplicationController
|
|
185
|
+
def show
|
|
186
|
+
service = Post::Find.run(service_args(id: params[:id]))
|
|
187
|
+
render_service service, serializer: PostSerializer
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Handling Different Error Types
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
def render_service(service, **options)
|
|
196
|
+
if service.success?
|
|
197
|
+
render_service_success(service, options)
|
|
198
|
+
else
|
|
199
|
+
status = determine_error_status(service, options)
|
|
200
|
+
render json: { errors: service.errors.to_h }, status: status
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def determine_error_status(service, options)
|
|
207
|
+
return options[:error_status] if options[:error_status]
|
|
208
|
+
|
|
209
|
+
# Map specific error keys to HTTP statuses
|
|
210
|
+
return :not_found if service.errors[:record]&.any?
|
|
211
|
+
return :forbidden if service.errors[:authorization]&.any?
|
|
212
|
+
return :unauthorized if service.errors[:authentication]&.any?
|
|
213
|
+
|
|
214
|
+
:unprocessable_entity
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## What's Next?
|
|
219
|
+
|
|
220
|
+
Learn how to integrate Pundit authorization with Light Services:
|
|
221
|
+
|
|
222
|
+
[Next: Pundit Authorization](pundit-authorization.md)
|
data/docs/steps.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Steps
|
|
2
|
+
|
|
3
|
+
Steps are the core components of a service, each representing a unit of work executed in sequence when the service is called.
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
- Define steps using the `step` keyword within the service class
|
|
8
|
+
- Use `if` and `unless` options for conditional steps
|
|
9
|
+
- Inherit steps from parent classes
|
|
10
|
+
- Inject steps into the execution flow with `before` and `after` options
|
|
11
|
+
- Ensure cleanup steps run with the `always: true` option (unless `done!` was called)
|
|
12
|
+
- Use a `run` method as a simple alternative for single-step services
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class GeneralParserService < ApplicationService
|
|
16
|
+
step :create_browser, unless: :browser
|
|
17
|
+
step :parse_content
|
|
18
|
+
step :quit_browser, always: true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class ParsePage < GeneralParserService
|
|
22
|
+
step :parse_additional_content, after: :parse_content
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Define Steps
|
|
27
|
+
|
|
28
|
+
Steps are declared using the `step` keyword in your service class.
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class User::Charge < ApplicationService
|
|
32
|
+
step :authorize
|
|
33
|
+
step :charge
|
|
34
|
+
step :send_email_receipt
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def authorize
|
|
39
|
+
# ...
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def charge
|
|
43
|
+
# ...
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def send_email_receipt
|
|
47
|
+
# ...
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Conditional Steps
|
|
53
|
+
|
|
54
|
+
Steps can be conditional, executed based on specified conditions using the `if` or `unless` keywords.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
class User::Charge < ApplicationService
|
|
58
|
+
step :authorize
|
|
59
|
+
step :charge
|
|
60
|
+
step :send_email_receipt, if: :send_receipt?
|
|
61
|
+
|
|
62
|
+
# ...
|
|
63
|
+
|
|
64
|
+
def send_receipt?
|
|
65
|
+
rand(2).zero?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This feature works well with argument predicates.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
class User::Charge < ApplicationService
|
|
74
|
+
arg :send_receipt, type: [TrueClass, FalseClass], default: true
|
|
75
|
+
|
|
76
|
+
step :send_email_receipt, if: :send_receipt?
|
|
77
|
+
|
|
78
|
+
# ...
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Using Procs for Conditions
|
|
83
|
+
|
|
84
|
+
You can also use Procs (lambdas) for inline conditions:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
class User::Charge < ApplicationService
|
|
88
|
+
arg :amount, type: Float
|
|
89
|
+
|
|
90
|
+
step :apply_discount, if: -> { amount > 100 }
|
|
91
|
+
step :charge
|
|
92
|
+
step :send_large_purchase_alert, if: -> { amount > 1000 }
|
|
93
|
+
|
|
94
|
+
# ...
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
{% hint style="info" %}
|
|
99
|
+
Using Procs can make simple conditions more readable, but for complex logic, prefer extracting to a method.
|
|
100
|
+
{% endhint %}
|
|
101
|
+
|
|
102
|
+
## Inheritance
|
|
103
|
+
|
|
104
|
+
Steps are inherited from parent classes, making it easy to build upon existing services.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# UpdateRecordService
|
|
108
|
+
class UpdateRecordService < ApplicationService
|
|
109
|
+
arg :record, type: ApplicationRecord
|
|
110
|
+
arg :attributes, type: Hash
|
|
111
|
+
|
|
112
|
+
step :authorize
|
|
113
|
+
step :update_record
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# User::Update inherited from UpdateRecordService
|
|
119
|
+
class User::Update < UpdateRecordService
|
|
120
|
+
# Arguments and steps are inherited from UpdateRecordService
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Injecting Steps into Execution Flow
|
|
125
|
+
|
|
126
|
+
Steps can be injected at specific points in the execution flow using `before` and `after` options.
|
|
127
|
+
|
|
128
|
+
Let's enhance the previous example by adding a step to send a notification after updating the record.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# User::Update inherited from UpdateRecordService
|
|
132
|
+
class User::Update < UpdateRecordService
|
|
133
|
+
step :log_action, before: :authorize
|
|
134
|
+
step :send_notification, after: :update_record
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def log_action
|
|
139
|
+
# ...
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def send_notification
|
|
143
|
+
# ...
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Combine this with `if` and `unless` options for more control.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
step :send_notification, after: :update_record, if: :send_notification?
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
{% hint style="info" %}
|
|
155
|
+
By default, if neither `before` nor `after` is specified, the step is added at the end of the execution flow.
|
|
156
|
+
{% endhint %}
|
|
157
|
+
|
|
158
|
+
## Always Running Steps
|
|
159
|
+
|
|
160
|
+
To ensure certain steps run regardless of previous step outcomes (errors, warnings, failed validations), use the `always: true` option. This is particularly useful for cleanup tasks, error logging, etc.
|
|
161
|
+
|
|
162
|
+
Note: if `done!` was called, the service exits early and `always: true` steps will **not** run.
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
class ParsePage < ApplicationService
|
|
166
|
+
arg :url, type: String
|
|
167
|
+
|
|
168
|
+
step :create_browser
|
|
169
|
+
step :parse_content
|
|
170
|
+
step :quit_browser, always: true
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
attr_accessor :browser
|
|
175
|
+
|
|
176
|
+
def create_browser
|
|
177
|
+
self.browser = Watir::Browser.new
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_content
|
|
181
|
+
# ...
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def quit_browser
|
|
185
|
+
browser&.quit
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Early Exit with `done!`
|
|
191
|
+
|
|
192
|
+
Use `done!` to stop executing remaining steps without adding an error. This is useful when you've completed the service's goal early and don't need to run subsequent steps.
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
class User::FindOrCreate < ApplicationService
|
|
196
|
+
arg :email, type: String
|
|
197
|
+
|
|
198
|
+
step :find_existing_user
|
|
199
|
+
step :create_user
|
|
200
|
+
step :send_welcome_email
|
|
201
|
+
|
|
202
|
+
output :user
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def find_existing_user
|
|
207
|
+
self.user = User.find_by(email:)
|
|
208
|
+
done! if user # Skip remaining steps if user already exists
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def create_user
|
|
212
|
+
self.user = User.create!(email:)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def send_welcome_email
|
|
216
|
+
# Only runs for newly created users
|
|
217
|
+
Mailer.welcome(user).deliver_later
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
You can check if `done!` was called using `done?`:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
def some_step
|
|
226
|
+
done!
|
|
227
|
+
|
|
228
|
+
# This code still runs within the same step
|
|
229
|
+
puts "Done? #{done?}" # => "Done? true"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def next_step
|
|
233
|
+
# This step will NOT run because done! was called
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
{% hint style="info" %}
|
|
238
|
+
`done!` stops subsequent steps from running, including steps marked with `always: true`. Code after `done!` within the same step method will still execute.
|
|
239
|
+
{% endhint %}
|
|
240
|
+
|
|
241
|
+
## Removing Inherited Steps
|
|
242
|
+
|
|
243
|
+
When inheriting from a parent service, you can remove steps using `remove_step`:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
class UpdateRecordService < ApplicationService
|
|
247
|
+
step :authorize
|
|
248
|
+
step :validate
|
|
249
|
+
step :update_record
|
|
250
|
+
step :send_notification
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
class InternalUpdate < UpdateRecordService
|
|
254
|
+
# Remove authorization for internal system updates
|
|
255
|
+
remove_step :authorize
|
|
256
|
+
remove_step :send_notification
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Using `run` Method as a Simple Alternative
|
|
261
|
+
|
|
262
|
+
For simple services that don't need multiple steps, you can define a `run` method instead of using the `step` DSL. If no steps are defined, Light Services will automatically use the `run` method as a single step.
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
class User::SendWelcomeEmail < ApplicationService
|
|
266
|
+
arg :user, type: User
|
|
267
|
+
|
|
268
|
+
private
|
|
269
|
+
|
|
270
|
+
def run
|
|
271
|
+
Mailer.welcome(user).deliver_later
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
This is equivalent to:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
class User::SendWelcomeEmail < ApplicationService
|
|
280
|
+
arg :user, type: User
|
|
281
|
+
|
|
282
|
+
step :run
|
|
283
|
+
|
|
284
|
+
private
|
|
285
|
+
|
|
286
|
+
def run
|
|
287
|
+
Mailer.welcome(user).deliver_later
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Inheritance with `run` Method
|
|
293
|
+
|
|
294
|
+
The `run` method works with inheritance. If a parent service defines a `run` method, child services will inherit it:
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
class BaseNotificationService < ApplicationService
|
|
298
|
+
arg :message, type: String
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
def run
|
|
303
|
+
send_notification(message)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def send_notification(msg)
|
|
307
|
+
raise NotImplementedError
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
class SlackNotification < BaseNotificationService
|
|
312
|
+
private
|
|
313
|
+
|
|
314
|
+
def send_notification(msg)
|
|
315
|
+
SlackClient.post(msg)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
class EmailNotification < BaseNotificationService
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
def send_notification(msg)
|
|
323
|
+
Mailer.notify(msg).deliver_later
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
{% hint style="info" %}
|
|
329
|
+
If a service has no steps defined and no `run` method (including from parent classes), a `Light::Services::NoStepsError` will be raised when the service is executed.
|
|
330
|
+
{% endhint %}
|
|
331
|
+
|
|
332
|
+
# What's Next?
|
|
333
|
+
|
|
334
|
+
Next step is to learn about outputs. Outputs are the results of a service, returned upon completion of service execution.
|
|
335
|
+
|
|
336
|
+
[Next: Outputs](outputs.md)
|
|
337
|
+
|
data/docs/summary.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Table of contents
|
|
2
|
+
|
|
3
|
+
* [Light Services](README.md)
|
|
4
|
+
* [Quickstart](quickstart.md)
|
|
5
|
+
* [Concepts](concepts.md)
|
|
6
|
+
* [Arguments](arguments.md)
|
|
7
|
+
* [Steps](steps.md)
|
|
8
|
+
* [Outputs](outputs.md)
|
|
9
|
+
* [Context](context.md)
|
|
10
|
+
* [Errors](errors.md)
|
|
11
|
+
* [Callbacks](callbacks.md)
|
|
12
|
+
* [Configuration](configuration.md)
|
|
13
|
+
* [Testing](testing.md)
|
|
14
|
+
* [Rails Generators](generators.md)
|
|
15
|
+
* [Best Practices](best-practices.md)
|
|
16
|
+
* [Recipes](recipes.md)
|
|
17
|
+
* [CRUD](crud.md)
|
|
18
|
+
* [Service Rendering](service-rendering.md)
|
|
19
|
+
* [Pundit Authorization](pundit-authorization.md)
|