operandi 5.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 +7 -0
- data/.cursor/rules/services/RULE.md +269 -0
- data/.cursor/rules/services-rspec/RULE.md +354 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +77 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.rubocop.yml +134 -0
- data/.ruby-version +1 -0
- data/.vscode/cspell.json +18 -0
- data/.vscode/project-words.txt +12 -0
- data/AGENTS.md +139 -0
- data/CHANGELOG.md +111 -0
- data/CLAUDE.md +139 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +149 -0
- data/LICENSE.txt +21 -0
- data/README.md +172 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/default.yml +57 -0
- data/docs/README.md +105 -0
- data/docs/SUMMARY.md +31 -0
- data/docs/arguments.md +275 -0
- data/docs/best-practices.md +153 -0
- data/docs/callbacks.md +475 -0
- data/docs/concepts.md +79 -0
- data/docs/configuration.md +218 -0
- data/docs/context.md +128 -0
- data/docs/crud.md +525 -0
- data/docs/errors.md +331 -0
- data/docs/generators.md +250 -0
- data/docs/outputs.md +150 -0
- data/docs/pundit-authorization.md +320 -0
- data/docs/quickstart.md +133 -0
- data/docs/recipes.md +14 -0
- data/docs/rubocop.md +430 -0
- data/docs/ruby-lsp.md +121 -0
- data/docs/service-rendering.md +222 -0
- data/docs/sorbet-runtime.md +283 -0
- data/docs/steps.md +438 -0
- data/docs/tapioca.md +188 -0
- data/docs/testing.md +548 -0
- data/lib/generators/operandi/install/USAGE +15 -0
- data/lib/generators/operandi/install/install_generator.rb +45 -0
- data/lib/generators/operandi/install/templates/application_service.rb.tt +8 -0
- data/lib/generators/operandi/install/templates/application_service_spec.rb.tt +7 -0
- data/lib/generators/operandi/install/templates/initializer.rb.tt +30 -0
- data/lib/generators/operandi/service/USAGE +21 -0
- data/lib/generators/operandi/service/service_generator.rb +80 -0
- data/lib/generators/operandi/service/templates/service.rb.tt +48 -0
- data/lib/generators/operandi/service/templates/service_spec.rb.tt +40 -0
- data/lib/operandi/base.rb +230 -0
- data/lib/operandi/base_with_context.rb +57 -0
- data/lib/operandi/callbacks.rb +353 -0
- data/lib/operandi/collection.rb +166 -0
- data/lib/operandi/concerns/execution.rb +80 -0
- data/lib/operandi/concerns/parent_service.rb +32 -0
- data/lib/operandi/concerns/state_management.rb +34 -0
- data/lib/operandi/config.rb +142 -0
- data/lib/operandi/constants.rb +96 -0
- data/lib/operandi/dsl/arguments_dsl.rb +83 -0
- data/lib/operandi/dsl/outputs_dsl.rb +79 -0
- data/lib/operandi/dsl/steps_dsl.rb +206 -0
- data/lib/operandi/dsl/validation.rb +171 -0
- data/lib/operandi/exceptions.rb +66 -0
- data/lib/operandi/message.rb +52 -0
- data/lib/operandi/messages.rb +185 -0
- data/lib/operandi/rspec/matchers/define_argument.rb +172 -0
- data/lib/operandi/rspec/matchers/define_output.rb +145 -0
- data/lib/operandi/rspec/matchers/define_step.rb +223 -0
- data/lib/operandi/rspec/matchers/execute_step.rb +228 -0
- data/lib/operandi/rspec/matchers/have_error_on.rb +144 -0
- data/lib/operandi/rspec/matchers/have_warning_on.rb +146 -0
- data/lib/operandi/rspec/matchers/trigger_callback.rb +136 -0
- data/lib/operandi/rspec.rb +15 -0
- data/lib/operandi/rubocop/cop/operandi/argument_type_required.rb +52 -0
- data/lib/operandi/rubocop/cop/operandi/condition_method_exists.rb +173 -0
- data/lib/operandi/rubocop/cop/operandi/deprecated_accessors.rb +113 -0
- data/lib/operandi/rubocop/cop/operandi/deprecated_methods.rb +113 -0
- data/lib/operandi/rubocop/cop/operandi/dsl_order.rb +181 -0
- data/lib/operandi/rubocop/cop/operandi/missing_private_keyword.rb +102 -0
- data/lib/operandi/rubocop/cop/operandi/no_direct_instantiation.rb +66 -0
- data/lib/operandi/rubocop/cop/operandi/no_hash_argument.rb +101 -0
- data/lib/operandi/rubocop/cop/operandi/output_type_required.rb +52 -0
- data/lib/operandi/rubocop/cop/operandi/prefer_fail_method.rb +112 -0
- data/lib/operandi/rubocop/cop/operandi/prefer_optional_over_default_nil.rb +124 -0
- data/lib/operandi/rubocop/cop/operandi/redundant_optional.rb +103 -0
- data/lib/operandi/rubocop/cop/operandi/reserved_name.rb +56 -0
- data/lib/operandi/rubocop/cop/operandi/step_method_exists.rb +134 -0
- data/lib/operandi/rubocop.rb +22 -0
- data/lib/operandi/settings/field.rb +147 -0
- data/lib/operandi/settings/step.rb +105 -0
- data/lib/operandi/utils.rb +36 -0
- data/lib/operandi/version.rb +5 -0
- data/lib/operandi.rb +11 -0
- data/lib/ruby_lsp/operandi/addon.rb +37 -0
- data/lib/ruby_lsp/operandi/definition.rb +134 -0
- data/lib/ruby_lsp/operandi/indexing_enhancement.rb +224 -0
- data/lib/tapioca/dsl/compilers/operandi.rb +378 -0
- data/operandi.gemspec +33 -0
- data/rbi/operandi.rbi +197 -0
- data/sorbet/cache/data.mdb +0 -0
- data/sorbet/cache/lock.mdb +0 -0
- metadata +151 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Pundit Authorization
|
|
2
|
+
|
|
3
|
+
[Pundit](https://github.com/varvet/pundit) is a simple, flexible authorization library for Ruby on Rails. This recipe shows how to integrate Pundit authorization into your Operandi.
|
|
4
|
+
|
|
5
|
+
## Why Use Pundit with Services?
|
|
6
|
+
|
|
7
|
+
- **Centralized authorization**: Keep authorization logic in policy classes
|
|
8
|
+
- **Consistent patterns**: Same authorization approach across controllers and services
|
|
9
|
+
- **Testable**: Policies are easy to unit test
|
|
10
|
+
- **Reusable**: Services can be called from controllers, jobs, or other services with consistent authorization
|
|
11
|
+
|
|
12
|
+
## Basic Setup
|
|
13
|
+
|
|
14
|
+
### 1. Create an Authorization Concern
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# app/services/concerns/authorize_user.rb
|
|
18
|
+
module AuthorizeUser
|
|
19
|
+
extend ActiveSupport::Concern
|
|
20
|
+
|
|
21
|
+
included do
|
|
22
|
+
arg :current_user, type: User, optional: true, context: true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Authorize an action on a record or class
|
|
26
|
+
def auth(record, action)
|
|
27
|
+
policy = policy(record)
|
|
28
|
+
|
|
29
|
+
unless policy.public_send(action)
|
|
30
|
+
errors.add(:authorization, "You are not authorized to perform this action")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
alias_method :authorize!, :auth
|
|
34
|
+
|
|
35
|
+
# Get permitted attributes for an action
|
|
36
|
+
def permitted_attributes(record, action = nil)
|
|
37
|
+
policy = policy(record)
|
|
38
|
+
|
|
39
|
+
method_name = if action
|
|
40
|
+
"permitted_attributes_for_#{action}"
|
|
41
|
+
else
|
|
42
|
+
"permitted_attributes"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if policy.respond_to?(method_name)
|
|
46
|
+
attributes = policy.public_send(method_name)
|
|
47
|
+
params.require(param_key(record)).permit(*attributes)
|
|
48
|
+
else
|
|
49
|
+
raise Pundit::NotDefinedError, "#{method_name} not defined in #{policy.class}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def policy(record)
|
|
56
|
+
Pundit.policy!(current_user, record)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def param_key(record)
|
|
60
|
+
if record.is_a?(Class)
|
|
61
|
+
record.model_name.param_key
|
|
62
|
+
else
|
|
63
|
+
record.model_name.param_key
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Include in ApplicationService
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# app/services/application_service.rb
|
|
73
|
+
class ApplicationService < Operandi::Base
|
|
74
|
+
include AuthorizeUser
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage Examples
|
|
79
|
+
|
|
80
|
+
### Authorizing Actions
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class Post::Update < ApplicationService
|
|
84
|
+
arg :post, type: Post
|
|
85
|
+
arg :attributes, type: Hash
|
|
86
|
+
|
|
87
|
+
step :authorize
|
|
88
|
+
step :update_post
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def authorize
|
|
93
|
+
auth(post, :update?)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def update_post
|
|
97
|
+
post.update!(attributes)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Authorizing on a Class (for Create actions)
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
class Post::Create < ApplicationService
|
|
106
|
+
arg :attributes, type: Hash
|
|
107
|
+
|
|
108
|
+
step :authorize
|
|
109
|
+
step :create_post
|
|
110
|
+
|
|
111
|
+
output :post
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def authorize
|
|
116
|
+
auth(Post, :create?)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def create_post
|
|
120
|
+
self.post = Post.create!(attributes)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Using Permitted Attributes
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
class Post::Create < ApplicationService
|
|
129
|
+
step :authorize
|
|
130
|
+
step :create_post
|
|
131
|
+
|
|
132
|
+
output :post
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def authorize
|
|
137
|
+
auth(Post, :create?)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def create_post
|
|
141
|
+
self.post = Post.create!(permitted_attributes(Post, :create))
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Policy Example
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
# app/policies/post_policy.rb
|
|
150
|
+
class PostPolicy < ApplicationPolicy
|
|
151
|
+
def create?
|
|
152
|
+
!user.nil?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def update?
|
|
156
|
+
!user.nil? && (record.author == user || user.admin?)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def destroy?
|
|
160
|
+
!user.nil? && (record.author == user || user.admin?)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def permitted_attributes_for_create
|
|
164
|
+
[:title, :body, :category_id]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def permitted_attributes_for_update
|
|
168
|
+
attributes = [:title, :body]
|
|
169
|
+
attributes << :category_id if user.admin?
|
|
170
|
+
attributes
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Integration with CRUD Services
|
|
176
|
+
|
|
177
|
+
Combine Pundit with the [CRUD recipe](crud.md) for powerful, authorized services:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# app/services/create_record_service.rb
|
|
181
|
+
class CreateRecordService < ApplicationService
|
|
182
|
+
arg :record_class, type: Class
|
|
183
|
+
arg :attributes, type: Hash, default: {}
|
|
184
|
+
|
|
185
|
+
step :authorize
|
|
186
|
+
step :create_record
|
|
187
|
+
|
|
188
|
+
output :record
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def authorize
|
|
193
|
+
auth(record_class, :create?)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def create_record
|
|
197
|
+
attrs = permitted_attributes(record_class, :create)
|
|
198
|
+
.to_h
|
|
199
|
+
.merge(attributes)
|
|
200
|
+
|
|
201
|
+
self.record = record_class.create!(attrs)
|
|
202
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
203
|
+
errors.copy_from(e.record)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Handling Authorization Failures
|
|
209
|
+
|
|
210
|
+
### Option 1: Collect as Errors (Default)
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
def authorize
|
|
214
|
+
auth(record, :update?)
|
|
215
|
+
# If unauthorized, an error is added and subsequent steps are skipped
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Option 2: Raise Exceptions
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
def authorize
|
|
223
|
+
unless policy(record).update?
|
|
224
|
+
raise Pundit::NotAuthorizedError, "not authorized to update this record"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Option 3: Custom Error Handling
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
def authorize
|
|
233
|
+
return if policy(record).update?
|
|
234
|
+
|
|
235
|
+
errors.add(:base, I18n.t("pundit.not_authorized"))
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Testing Services with Authorization
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
RSpec.describe Post::Update do
|
|
243
|
+
let(:author) { create(:user) }
|
|
244
|
+
let(:other_user) { create(:user) }
|
|
245
|
+
let(:post) { create(:post, author: author) }
|
|
246
|
+
|
|
247
|
+
context "when user is the author" do
|
|
248
|
+
it "updates the post" do
|
|
249
|
+
service = described_class.run(
|
|
250
|
+
current_user: author,
|
|
251
|
+
post: post,
|
|
252
|
+
attributes: { title: "New Title" }
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
expect(service).to be_successful
|
|
256
|
+
expect(post.reload.title).to eq("New Title")
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
context "when user is not the author" do
|
|
261
|
+
it "returns authorization error" do
|
|
262
|
+
service = described_class.run(
|
|
263
|
+
current_user: other_user,
|
|
264
|
+
post: post,
|
|
265
|
+
attributes: { title: "New Title" }
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(service).to be_failed
|
|
269
|
+
expect(service.errors[:authorization]).to be_present
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
context "when no user is provided" do
|
|
274
|
+
it "returns authorization error" do
|
|
275
|
+
service = described_class.run(
|
|
276
|
+
current_user: nil,
|
|
277
|
+
post: post,
|
|
278
|
+
attributes: { title: "New Title" }
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
expect(service).to be_failed
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Skipping Authorization
|
|
288
|
+
|
|
289
|
+
For system-level operations or background jobs where authorization isn't needed:
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
class Post::SystemUpdate < UpdateRecordService
|
|
293
|
+
# Remove the authorize step for system operations
|
|
294
|
+
remove_step :authorize
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Or create a "system" flag:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
class ApplicationService < Operandi::Base
|
|
302
|
+
arg :system, type: [TrueClass, FalseClass], default: false, context: true
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
class Post::Update < ApplicationService
|
|
306
|
+
step :authorize, unless: :system?
|
|
307
|
+
step :update_post
|
|
308
|
+
|
|
309
|
+
# ...
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Usage in background job
|
|
313
|
+
Post::Update.run(post: post, attributes: attrs, system: true)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## What's Next?
|
|
317
|
+
|
|
318
|
+
Return to the recipes overview:
|
|
319
|
+
|
|
320
|
+
[Back to Recipes](recipes.md)
|
data/docs/quickstart.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
Operandi are framework-agnostic and can be used in any Ruby project.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "operandi", "~> 3.0"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or you can install it yourself by running:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle add operandi --version "~> 3.0"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Create `ApplicationService`
|
|
20
|
+
|
|
21
|
+
{% hint style="info" %}
|
|
22
|
+
This step is optional but recommended. Creating a base class for your services can help organize your code. This base class will act as the parent for all your services, where you can include common logic such as helpers, logging, error handling, etc.
|
|
23
|
+
{% endhint %}
|
|
24
|
+
|
|
25
|
+
### For Rails Applications
|
|
26
|
+
|
|
27
|
+
If you're using Rails, you can use the install generator to set up Operandi automatically:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bin/rails generate operandi:install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This will create the `ApplicationService` base class, an initializer, and a spec file (if RSpec is detected). See [Rails Generators](generators.md) for more details.
|
|
34
|
+
|
|
35
|
+
### For Non-Rails Applications
|
|
36
|
+
|
|
37
|
+
First, create a folder for your services. The path will depend on the framework you are using. For Rails, you can create a folder in `app/services`.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
mkdir app/services
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Next, create your base class. You can name it as you wish, but we recommend `ApplicationService`.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# app/services/application_service.rb
|
|
47
|
+
class ApplicationService < Operandi::Base
|
|
48
|
+
# Add common arguments, callbacks, or helpers shared across all services.
|
|
49
|
+
#
|
|
50
|
+
# Example: Add a context argument for the current user
|
|
51
|
+
# arg :current_user, type: User, optional: true, context: true
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Create Your First Service
|
|
56
|
+
|
|
57
|
+
Now let's create our first service. We'll make a simple service that returns a greeting message.
|
|
58
|
+
|
|
59
|
+
{% hint style="info" %}
|
|
60
|
+
**Rails users:** You can use the service generator to create services quickly:
|
|
61
|
+
```bash
|
|
62
|
+
bin/rails generate operandi:service GreetService --args=name --steps=greet --outputs=greeted
|
|
63
|
+
```
|
|
64
|
+
See [Rails Generators](generators.md) for more information.
|
|
65
|
+
{% endhint %}
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# app/services/greet_service.rb
|
|
69
|
+
class GreetService < ApplicationService
|
|
70
|
+
# Arguments
|
|
71
|
+
arg :name, type: String
|
|
72
|
+
|
|
73
|
+
# Steps
|
|
74
|
+
step :greet
|
|
75
|
+
|
|
76
|
+
# Outputs
|
|
77
|
+
output :greeted, type: [TrueClass, FalseClass], default: false
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def greet
|
|
82
|
+
puts "Hello, #{name}!"
|
|
83
|
+
self.greeted = true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Run the Service
|
|
89
|
+
|
|
90
|
+
Now you can run your service from anywhere in your application.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
service = GreetService.run(name: "John")
|
|
94
|
+
service.greeted # => true
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Check for Success or Failure
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
service = GreetService.run(name: "John")
|
|
101
|
+
|
|
102
|
+
if service.success?
|
|
103
|
+
puts "Greeting sent!"
|
|
104
|
+
puts service.greeted
|
|
105
|
+
else
|
|
106
|
+
puts "Failed: #{service.errors.to_h}"
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Raise on Error with `run!`
|
|
111
|
+
|
|
112
|
+
Use `run!` when you want errors to raise exceptions instead of being collected:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# This will raise Operandi::RuntimeError if any errors are added
|
|
116
|
+
service = GreetService.run!(name: "John")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This is equivalent to:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
service = GreetService.run({ name: "John" }, { raise_on_error: true })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
{% hint style="info" %}
|
|
126
|
+
Looks easy, right? But this is just the beginning. Operandi can do much more 🚀
|
|
127
|
+
{% endhint %}
|
|
128
|
+
|
|
129
|
+
## What's Next?
|
|
130
|
+
|
|
131
|
+
Learn how to configure Operandi for your application:
|
|
132
|
+
|
|
133
|
+
[Next: Configuration](configuration.md)
|
data/docs/recipes.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
This section contains practical recipes for common patterns when using Operandi.
|
|
4
|
+
|
|
5
|
+
- [CRUD](crud.md) - Implementing top-level services for CRUD operations
|
|
6
|
+
- [Service Rendering](service-rendering.md) - Easy way to render service results and errors
|
|
7
|
+
- [Pundit Authorization](pundit-authorization.md) - Integrating Pundit authorization with Operandi
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
These recipes build upon each other. We recommend starting with CRUD services, then adding service rendering for cleaner controllers, and finally integrating Pundit for authorization.
|
|
12
|
+
|
|
13
|
+
[Start with CRUD](crud.md)
|
|
14
|
+
|