servus 0.3.0 → 0.4.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/lib/generators/servus/event_handler/event_handler_generator.rb +1 -1
- data/lib/generators/servus/guard/guard_generator.rb +1 -1
- data/lib/generators/servus/guard/templates/guard.rb.erb +5 -3
- data/lib/generators/servus/service/service_generator.rb +1 -1
- data/lib/servus/base.rb +46 -3
- data/lib/servus/config.rb +71 -3
- data/lib/servus/events/bus.rb +29 -0
- data/lib/servus/events/emitter.rb +15 -0
- data/lib/servus/guard.rb +7 -6
- data/lib/servus/guards/falsey_guard.rb +3 -3
- data/lib/servus/guards/presence_guard.rb +4 -4
- data/lib/servus/guards/state_guard.rb +4 -5
- data/lib/servus/guards/truthy_guard.rb +3 -3
- data/lib/servus/helpers/controller_helpers.rb +40 -0
- data/lib/servus/support/errors.rb +16 -0
- data/lib/servus/support/lockdown.rb +94 -0
- data/lib/servus/support/logger.rb +16 -0
- data/lib/servus/support/validator.rb +65 -34
- data/lib/servus/testing/example_builders.rb +52 -0
- data/lib/servus/testing/matchers.rb +99 -0
- data/lib/servus/version.rb +1 -1
- data/lib/servus.rb +1 -0
- metadata +7 -111
- data/.claude/commands/check-docs.md +0 -1
- data/.claude/commands/consistency-check.md +0 -1
- data/.claude/commands/fine-tooth-comb.md +0 -1
- data/.claude/commands/red-green-refactor.md +0 -5
- data/.claude/settings.json +0 -24
- data/.rspec +0 -3
- data/.rubocop.yml +0 -27
- data/.yardopts +0 -6
- data/CHANGELOG.md +0 -169
- data/CLAUDE.md +0 -10
- data/IDEAS.md +0 -5
- data/LICENSE.txt +0 -21
- data/READme.md +0 -856
- data/Rakefile +0 -45
- data/docs/core/1_overview.md +0 -81
- data/docs/core/2_architecture.md +0 -120
- data/docs/core/3_service_objects.md +0 -154
- data/docs/features/1_schema_validation.md +0 -161
- data/docs/features/2_error_handling.md +0 -129
- data/docs/features/3_async_execution.md +0 -81
- data/docs/features/4_logging.md +0 -64
- data/docs/features/5_event_bus.md +0 -244
- data/docs/features/6_guards.md +0 -356
- data/docs/features/7_lazy_resolvers.md +0 -238
- data/docs/features/guards_naming_convention.md +0 -540
- data/docs/guides/1_common_patterns.md +0 -90
- data/docs/guides/2_migration_guide.md +0 -225
- data/docs/integration/1_configuration.md +0 -154
- data/docs/integration/2_testing.md +0 -304
- data/docs/integration/3_rails_integration.md +0 -99
- data/docs/yard/Servus/Base.html +0 -1645
- data/docs/yard/Servus/Config.html +0 -582
- data/docs/yard/Servus/Extensions/Async/Call.html +0 -400
- data/docs/yard/Servus/Extensions/Async/Errors/AsyncError.html +0 -140
- data/docs/yard/Servus/Extensions/Async/Errors/JobEnqueueError.html +0 -154
- data/docs/yard/Servus/Extensions/Async/Errors/ServiceNotFoundError.html +0 -154
- data/docs/yard/Servus/Extensions/Async/Errors.html +0 -128
- data/docs/yard/Servus/Extensions/Async/Ext.html +0 -119
- data/docs/yard/Servus/Extensions/Async/Job.html +0 -310
- data/docs/yard/Servus/Extensions/Async.html +0 -141
- data/docs/yard/Servus/Extensions.html +0 -117
- data/docs/yard/Servus/Generators/ServiceGenerator.html +0 -261
- data/docs/yard/Servus/Generators.html +0 -115
- data/docs/yard/Servus/Helpers/ControllerHelpers.html +0 -457
- data/docs/yard/Servus/Helpers.html +0 -115
- data/docs/yard/Servus/Railtie.html +0 -134
- data/docs/yard/Servus/Support/Errors/AuthenticationError.html +0 -287
- data/docs/yard/Servus/Support/Errors/BadRequestError.html +0 -283
- data/docs/yard/Servus/Support/Errors/ForbiddenError.html +0 -284
- data/docs/yard/Servus/Support/Errors/InternalServerError.html +0 -283
- data/docs/yard/Servus/Support/Errors/NotFoundError.html +0 -284
- data/docs/yard/Servus/Support/Errors/ServiceError.html +0 -489
- data/docs/yard/Servus/Support/Errors/ServiceUnavailableError.html +0 -290
- data/docs/yard/Servus/Support/Errors/UnauthorizedError.html +0 -200
- data/docs/yard/Servus/Support/Errors/UnprocessableEntityError.html +0 -288
- data/docs/yard/Servus/Support/Errors/ValidationError.html +0 -200
- data/docs/yard/Servus/Support/Errors.html +0 -140
- data/docs/yard/Servus/Support/Logger.html +0 -856
- data/docs/yard/Servus/Support/Rescuer/BlockContext.html +0 -585
- data/docs/yard/Servus/Support/Rescuer/CallOverride.html +0 -257
- data/docs/yard/Servus/Support/Rescuer/ClassMethods.html +0 -343
- data/docs/yard/Servus/Support/Rescuer.html +0 -267
- data/docs/yard/Servus/Support/Response.html +0 -574
- data/docs/yard/Servus/Support/Validator.html +0 -1150
- data/docs/yard/Servus/Support.html +0 -119
- data/docs/yard/Servus/Testing/ExampleBuilders.html +0 -523
- data/docs/yard/Servus/Testing/ExampleExtractor.html +0 -578
- data/docs/yard/Servus/Testing.html +0 -142
- data/docs/yard/Servus.html +0 -343
- data/docs/yard/_index.html +0 -535
- data/docs/yard/class_list.html +0 -54
- data/docs/yard/css/common.css +0 -1
- data/docs/yard/css/full_list.css +0 -58
- data/docs/yard/css/style.css +0 -503
- data/docs/yard/file.1_common_patterns.html +0 -154
- data/docs/yard/file.1_configuration.html +0 -115
- data/docs/yard/file.1_overview.html +0 -142
- data/docs/yard/file.1_schema_validation.html +0 -188
- data/docs/yard/file.2_architecture.html +0 -157
- data/docs/yard/file.2_error_handling.html +0 -190
- data/docs/yard/file.2_migration_guide.html +0 -242
- data/docs/yard/file.2_testing.html +0 -227
- data/docs/yard/file.3_async_execution.html +0 -145
- data/docs/yard/file.3_rails_integration.html +0 -160
- data/docs/yard/file.3_service_objects.html +0 -191
- data/docs/yard/file.4_logging.html +0 -135
- data/docs/yard/file.ErrorHandling.html +0 -190
- data/docs/yard/file.READme.html +0 -674
- data/docs/yard/file.architecture.html +0 -157
- data/docs/yard/file.async_execution.html +0 -145
- data/docs/yard/file.common_patterns.html +0 -154
- data/docs/yard/file.configuration.html +0 -115
- data/docs/yard/file.error_handling.html +0 -190
- data/docs/yard/file.logging.html +0 -135
- data/docs/yard/file.migration_guide.html +0 -242
- data/docs/yard/file.overview.html +0 -142
- data/docs/yard/file.rails_integration.html +0 -160
- data/docs/yard/file.schema_validation.html +0 -188
- data/docs/yard/file.service_objects.html +0 -191
- data/docs/yard/file.testing.html +0 -227
- data/docs/yard/file_list.html +0 -119
- data/docs/yard/frames.html +0 -22
- data/docs/yard/index.html +0 -674
- data/docs/yard/js/app.js +0 -344
- data/docs/yard/js/full_list.js +0 -242
- data/docs/yard/js/jquery.js +0 -4
- data/docs/yard/method_list.html +0 -542
- data/docs/yard/top-level-namespace.html +0 -110
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
# Servus Guards: Final Naming Convention
|
|
2
|
-
|
|
3
|
-
## The Pattern
|
|
4
|
-
|
|
5
|
-
### Class Naming: `<Condition>Guard`
|
|
6
|
-
|
|
7
|
-
**Rule:** Guard class names should describe **what is being checked**, NOT the action.
|
|
8
|
-
|
|
9
|
-
- ✅ **DO** use nouns, adjectives, or states
|
|
10
|
-
- ❌ **DON'T** use action verbs like "Enforce", "Check", "Verify", "Require", "Assert"
|
|
11
|
-
|
|
12
|
-
### Generated Methods
|
|
13
|
-
|
|
14
|
-
The framework automatically generates two methods:
|
|
15
|
-
|
|
16
|
-
1. **Bang method:** `enforce_<condition>!` - Enforces the rule or throws
|
|
17
|
-
2. **Predicate method:** `check_<condition>?` - Checks if the rule is met
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## ✅ Good Examples
|
|
22
|
-
|
|
23
|
-
### Example 1: Balance Check
|
|
24
|
-
|
|
25
|
-
```ruby
|
|
26
|
-
# ✅ GOOD - Describes the condition
|
|
27
|
-
class SufficientBalanceGuard < Servus::Guard
|
|
28
|
-
message 'Insufficient balance: need %<required>s, have %<available>s' do
|
|
29
|
-
{ required: amount, available: account.balance }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test(account:, amount:)
|
|
33
|
-
account.balance >= amount
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Generates:
|
|
38
|
-
enforce_sufficient_balance!(account: account, amount: amount)
|
|
39
|
-
check_sufficient_balance?(account: account, amount: amount)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Why it's good:** "SufficientBalance" describes the state/condition being checked.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
### Example 2: Presence Check
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
# ✅ GOOD - Describes the condition
|
|
50
|
-
class PresenceGuard < Servus::Guard
|
|
51
|
-
message '%<keys>s must be present' do
|
|
52
|
-
{ keys: kwargs.keys.join(', ') }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def test(**values)
|
|
56
|
-
values.values.all?(&:present?)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Generates:
|
|
61
|
-
enforce_presence!(user: user, account: account)
|
|
62
|
-
check_presence?(user: user, account: account)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**Alternative naming:**
|
|
66
|
-
```ruby
|
|
67
|
-
# Also good
|
|
68
|
-
class NotNilGuard < Servus::Guard
|
|
69
|
-
# ...
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Generates:
|
|
73
|
-
enforce_not_nil!(user: user)
|
|
74
|
-
check_not_nil?(user: user)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
### Example 3: Authorization
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
# ✅ GOOD - Describes the requirement
|
|
83
|
-
class AdminRoleGuard < Servus::Guard
|
|
84
|
-
message 'User must have admin role' do
|
|
85
|
-
{}
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def test(user:)
|
|
89
|
-
user.admin?
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Generates:
|
|
94
|
-
enforce_admin_role!(user: user)
|
|
95
|
-
check_admin_role?(user: user)
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
### Example 4: Product Feature
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
# ✅ GOOD - Describes the enabled state
|
|
104
|
-
class EnabledProductGuard < Servus::Guard
|
|
105
|
-
message 'Product %<product_name>s is not enabled' do
|
|
106
|
-
{ product_name: product.name }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def test(product:)
|
|
110
|
-
product.enabled?
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Generates:
|
|
115
|
-
enforce_enabled_product!(product: product)
|
|
116
|
-
check_enabled_product?(product: product)
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
### Example 5: Age Requirement
|
|
122
|
-
|
|
123
|
-
```ruby
|
|
124
|
-
# ✅ GOOD - Describes the requirement
|
|
125
|
-
class MinimumAgeGuard < Servus::Guard
|
|
126
|
-
message 'Must be at least %<minimum>s years old' do
|
|
127
|
-
{ minimum: 18 }
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def test(date_of_birth:)
|
|
131
|
-
age = ((Time.zone.now - date_of_birth.to_time) / 1.year.seconds).floor
|
|
132
|
-
age >= 18
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Generates:
|
|
137
|
-
enforce_minimum_age!(date_of_birth: user.date_of_birth)
|
|
138
|
-
check_minimum_age?(date_of_birth: user.date_of_birth)
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
### Example 6: Rate Limiting
|
|
144
|
-
|
|
145
|
-
```ruby
|
|
146
|
-
# ✅ GOOD - Describes the limit state
|
|
147
|
-
class DailyLimitRemainingGuard < Servus::Guard
|
|
148
|
-
message 'Daily limit exceeded: %<used>s/%<limit>s' do
|
|
149
|
-
{
|
|
150
|
-
used: user.daily_api_calls,
|
|
151
|
-
limit: user.daily_api_limit
|
|
152
|
-
}
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def test(user:)
|
|
156
|
-
user.daily_api_calls < user.daily_api_limit
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Generates:
|
|
161
|
-
enforce_daily_limit_remaining!(user: user)
|
|
162
|
-
check_daily_limit_remaining?(user: user)
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
### Example 7: Ownership
|
|
168
|
-
|
|
169
|
-
```ruby
|
|
170
|
-
# ✅ GOOD - Describes the relationship
|
|
171
|
-
class OwnershipGuard < Servus::Guard
|
|
172
|
-
message 'User does not own this resource' do
|
|
173
|
-
{}
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def test(user:, resource:)
|
|
177
|
-
resource.user_id == user.id
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Generates:
|
|
182
|
-
enforce_ownership!(user: user, resource: account)
|
|
183
|
-
check_ownership?(user: user, resource: account)
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
## ❌ Bad Examples (DON'T DO THIS)
|
|
189
|
-
|
|
190
|
-
### Example 1: Using "Enforce" in Class Name
|
|
191
|
-
|
|
192
|
-
```ruby
|
|
193
|
-
# ❌ BAD - Uses action verb
|
|
194
|
-
class EnforceSufficientBalanceGuard < Servus::Guard
|
|
195
|
-
# ...
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Generates (redundant!):
|
|
199
|
-
enforce_enforce_sufficient_balance!(...) # ❌ Redundant!
|
|
200
|
-
check_enforce_sufficient_balance?(...) # ❌ Doesn't make sense!
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Why it's bad:** The action verb "Enforce" is already added by the framework.
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
### Example 2: Using "Check" in Class Name
|
|
208
|
-
|
|
209
|
-
```ruby
|
|
210
|
-
# ❌ BAD - Uses action verb
|
|
211
|
-
class CheckPresenceGuard < Servus::Guard
|
|
212
|
-
# ...
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Generates (redundant!):
|
|
216
|
-
enforce_check_presence!(...) # ❌ Weird!
|
|
217
|
-
check_check_presence?(...) # ❌ Redundant!
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Why it's bad:** The action verb "Check" is already added by the framework.
|
|
221
|
-
|
|
222
|
-
---
|
|
223
|
-
|
|
224
|
-
### Example 3: Using "Require" in Class Name
|
|
225
|
-
|
|
226
|
-
```ruby
|
|
227
|
-
# ❌ BAD - Uses action verb
|
|
228
|
-
class RequireAdminRoleGuard < Servus::Guard
|
|
229
|
-
# ...
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Generates (awkward!):
|
|
233
|
-
enforce_require_admin_role!(...) # ❌ Double action verbs!
|
|
234
|
-
check_require_admin_role?(...) # ❌ Confusing!
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Why it's bad:** "Require" is an action verb that conflicts with the framework's verbs.
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
### Example 4: Using "Verify" in Class Name
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
# ❌ BAD - Uses action verb
|
|
245
|
-
class VerifyOwnershipGuard < Servus::Guard
|
|
246
|
-
# ...
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Generates (awkward!):
|
|
250
|
-
enforce_verify_ownership!(...) # ❌ Double action verbs!
|
|
251
|
-
check_verify_ownership?(...) # ❌ Confusing!
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Why it's bad:** "Verify" is an action verb that conflicts with the framework.
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
### Example 5: Using "Validate" in Class Name
|
|
259
|
-
|
|
260
|
-
```ruby
|
|
261
|
-
# ❌ BAD - Uses action verb
|
|
262
|
-
class ValidateEmailGuard < Servus::Guard
|
|
263
|
-
# ...
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Generates (awkward!):
|
|
267
|
-
enforce_validate_email!(...) # ❌ Double action verbs!
|
|
268
|
-
check_validate_email?(...) # ❌ Confusing!
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
**Why it's bad:** "Validate" is an action verb.
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## 📝 Naming Guidelines
|
|
276
|
-
|
|
277
|
-
### DO: Use Descriptive Conditions
|
|
278
|
-
|
|
279
|
-
**Pattern:** `<Adjective><Noun>Guard` or `<Noun>Guard`
|
|
280
|
-
|
|
281
|
-
Examples:
|
|
282
|
-
- `SufficientBalanceGuard` - adjective + noun
|
|
283
|
-
- `PresenceGuard` - noun
|
|
284
|
-
- `AdminRoleGuard` - noun
|
|
285
|
-
- `EnabledProductGuard` - adjective + noun
|
|
286
|
-
- `MinimumAgeGuard` - adjective + noun
|
|
287
|
-
- `ActiveDeviceGuard` - adjective + noun
|
|
288
|
-
- `ValidEmailGuard` - adjective + noun
|
|
289
|
-
- `PositiveAmountGuard` - adjective + noun
|
|
290
|
-
- `UniqueEmailGuard` - adjective + noun
|
|
291
|
-
|
|
292
|
-
### DON'T: Use Action Verbs
|
|
293
|
-
|
|
294
|
-
**Avoid these prefixes:**
|
|
295
|
-
- ❌ `Enforce...Guard`
|
|
296
|
-
- ❌ `Check...Guard`
|
|
297
|
-
- ❌ `Verify...Guard`
|
|
298
|
-
- ❌ `Require...Guard`
|
|
299
|
-
- ❌ `Assert...Guard`
|
|
300
|
-
- ❌ `Validate...Guard`
|
|
301
|
-
- ❌ `Ensure...Guard`
|
|
302
|
-
- ❌ `Demand...Guard`
|
|
303
|
-
- ❌ `Test...Guard`
|
|
304
|
-
|
|
305
|
-
**Why:** The framework automatically adds `enforce_` and `check_` prefixes to the generated methods.
|
|
306
|
-
|
|
307
|
-
---
|
|
308
|
-
|
|
309
|
-
## 🎯 Naming Tips
|
|
310
|
-
|
|
311
|
-
### Tip 1: Think About the Condition, Not the Action
|
|
312
|
-
|
|
313
|
-
**Ask yourself:** "What state or condition am I checking?"
|
|
314
|
-
|
|
315
|
-
- ✅ "Is the balance sufficient?" → `SufficientBalanceGuard`
|
|
316
|
-
- ✅ "Is the user present?" → `PresenceGuard`
|
|
317
|
-
- ✅ "Does the user have admin role?" → `AdminRoleGuard`
|
|
318
|
-
- ✅ "Is the product enabled?" → `EnabledProductGuard`
|
|
319
|
-
|
|
320
|
-
**Don't ask:** "What action am I taking?"
|
|
321
|
-
|
|
322
|
-
- ❌ "I'm enforcing balance" → `EnforceBalanceGuard` (wrong!)
|
|
323
|
-
- ❌ "I'm checking presence" → `CheckPresenceGuard` (wrong!)
|
|
324
|
-
|
|
325
|
-
---
|
|
326
|
-
|
|
327
|
-
### Tip 2: Use Adjectives for States
|
|
328
|
-
|
|
329
|
-
When checking if something is in a certain state, use an adjective:
|
|
330
|
-
|
|
331
|
-
- `ActiveDeviceGuard` - device is active
|
|
332
|
-
- `ValidEmailGuard` - email is valid
|
|
333
|
-
- `PositiveAmountGuard` - amount is positive
|
|
334
|
-
- `UniqueEmailGuard` - email is unique
|
|
335
|
-
- `EnabledProductGuard` - product is enabled
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
### Tip 3: Use Nouns for Existence/Presence
|
|
340
|
-
|
|
341
|
-
When checking if something exists or is present:
|
|
342
|
-
|
|
343
|
-
- `PresenceGuard` - checks presence
|
|
344
|
-
- `OwnershipGuard` - checks ownership
|
|
345
|
-
- `AdminRoleGuard` - checks for admin role
|
|
346
|
-
- `PermissionGuard` - checks for permission
|
|
347
|
-
|
|
348
|
-
---
|
|
349
|
-
|
|
350
|
-
### Tip 4: Describe Requirements Positively
|
|
351
|
-
|
|
352
|
-
Prefer positive descriptions over negative:
|
|
353
|
-
|
|
354
|
-
- ✅ `SufficientBalanceGuard` (positive)
|
|
355
|
-
- ⚠️ `InsufficientBalanceGuard` (negative - works but less clear)
|
|
356
|
-
|
|
357
|
-
- ✅ `ActiveDeviceGuard` (positive)
|
|
358
|
-
- ⚠️ `InactiveDeviceGuard` (negative)
|
|
359
|
-
|
|
360
|
-
- ✅ `ValidEmailGuard` (positive)
|
|
361
|
-
- ⚠️ `InvalidEmailGuard` (negative)
|
|
362
|
-
|
|
363
|
-
**Exception:** Sometimes negative is clearer:
|
|
364
|
-
|
|
365
|
-
- `NotNilGuard` - clear and concise
|
|
366
|
-
- `NotEmptyGuard` - clear what it checks
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## 📚 Complete Examples Library
|
|
371
|
-
|
|
372
|
-
### Simple Validations
|
|
373
|
-
|
|
374
|
-
```ruby
|
|
375
|
-
class PresenceGuard < Servus::Guard
|
|
376
|
-
# enforce_presence! / check_presence?
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
class NotNilGuard < Servus::Guard
|
|
380
|
-
# enforce_not_nil! / check_not_nil?
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
class PositiveAmountGuard < Servus::Guard
|
|
384
|
-
# enforce_positive_amount! / check_positive_amount?
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
class ValidEmailGuard < Servus::Guard
|
|
388
|
-
# enforce_valid_email! / check_valid_email?
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
class UniqueEmailGuard < Servus::Guard
|
|
392
|
-
# enforce_unique_email! / check_unique_email?
|
|
393
|
-
end
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Business Rules
|
|
397
|
-
|
|
398
|
-
```ruby
|
|
399
|
-
class SufficientBalanceGuard < Servus::Guard
|
|
400
|
-
# enforce_sufficient_balance! / check_sufficient_balance?
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
class DailyLimitRemainingGuard < Servus::Guard
|
|
404
|
-
# enforce_daily_limit_remaining! / check_daily_limit_remaining?
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
class MinimumPurchaseAmountGuard < Servus::Guard
|
|
408
|
-
# enforce_minimum_purchase_amount! / check_minimum_purchase_amount?
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
class WithinTransferLimitGuard < Servus::Guard
|
|
412
|
-
# enforce_within_transfer_limit! / check_within_transfer_limit?
|
|
413
|
-
end
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Authorization
|
|
417
|
-
|
|
418
|
-
```ruby
|
|
419
|
-
class AdminRoleGuard < Servus::Guard
|
|
420
|
-
# enforce_admin_role! / check_admin_role?
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
class OwnershipGuard < Servus::Guard
|
|
424
|
-
# enforce_ownership! / check_ownership?
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
class PermissionGuard < Servus::Guard
|
|
428
|
-
# enforce_permission! / check_permission?
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
class ActiveMembershipGuard < Servus::Guard
|
|
432
|
-
# enforce_active_membership! / check_active_membership?
|
|
433
|
-
end
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### Resource States
|
|
437
|
-
|
|
438
|
-
```ruby
|
|
439
|
-
class ActiveDeviceGuard < Servus::Guard
|
|
440
|
-
# enforce_active_device! / check_active_device?
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
class EnabledProductGuard < Servus::Guard
|
|
444
|
-
# enforce_enabled_product! / check_enabled_product?
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
class AvailableInventoryGuard < Servus::Guard
|
|
448
|
-
# enforce_available_inventory! / check_available_inventory?
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
class OpenAccountGuard < Servus::Guard
|
|
452
|
-
# enforce_open_account! / check_open_account?
|
|
453
|
-
end
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Compliance
|
|
457
|
-
|
|
458
|
-
```ruby
|
|
459
|
-
class MinimumAgeGuard < Servus::Guard
|
|
460
|
-
# enforce_minimum_age! / check_minimum_age?
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
class CompletedKYCGuard < Servus::Guard
|
|
464
|
-
# enforce_completed_kyc! / check_completed_kyc?
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
class AcceptedTermsGuard < Servus::Guard
|
|
468
|
-
# enforce_accepted_terms! / check_accepted_terms?
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
class VerifiedEmailGuard < Servus::Guard
|
|
472
|
-
# enforce_verified_email! / check_verified_email?
|
|
473
|
-
end
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
---
|
|
477
|
-
|
|
478
|
-
## 🔄 Migration Guide
|
|
479
|
-
|
|
480
|
-
If you have existing guards with action verbs, here's how to rename them:
|
|
481
|
-
|
|
482
|
-
### Before (with action verbs)
|
|
483
|
-
```ruby
|
|
484
|
-
class EnforceSufficientBalanceGuard < Servus::Guard
|
|
485
|
-
# ...
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
# Usage:
|
|
489
|
-
enforce_enforce_sufficient_balance!(...) # Redundant!
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### After (condition only)
|
|
493
|
-
```ruby
|
|
494
|
-
class SufficientBalanceGuard < Servus::Guard
|
|
495
|
-
# ...
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
# Usage:
|
|
499
|
-
enforce_sufficient_balance!(...) # Clean!
|
|
500
|
-
check_sufficient_balance?(...) # Clear!
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
### Rename Mapping
|
|
504
|
-
|
|
505
|
-
| Old Name (❌) | New Name (✅) |
|
|
506
|
-
|--------------|--------------|
|
|
507
|
-
| `EnforceSufficientBalanceGuard` | `SufficientBalanceGuard` |
|
|
508
|
-
| `RequirePresenceGuard` | `PresenceGuard` |
|
|
509
|
-
| `CheckAdminRoleGuard` | `AdminRoleGuard` |
|
|
510
|
-
| `VerifyOwnershipGuard` | `OwnershipGuard` |
|
|
511
|
-
| `AssertPositiveGuard` | `PositiveAmountGuard` |
|
|
512
|
-
| `EnsureValidEmailGuard` | `ValidEmailGuard` |
|
|
513
|
-
| `DemandActiveDeviceGuard` | `ActiveDeviceGuard` |
|
|
514
|
-
|
|
515
|
-
---
|
|
516
|
-
|
|
517
|
-
## ✅ Summary
|
|
518
|
-
|
|
519
|
-
**The Golden Rule:**
|
|
520
|
-
|
|
521
|
-
> Guard class names describe **WHAT** is being checked, not **HOW** it's being checked.
|
|
522
|
-
|
|
523
|
-
**Pattern:**
|
|
524
|
-
```ruby
|
|
525
|
-
class <Condition>Guard < Servus::Guard
|
|
526
|
-
# Describes the condition/state/requirement
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
# Framework generates:
|
|
530
|
-
enforce_<condition>! # Action verb added by framework
|
|
531
|
-
check_<condition>? # Action verb added by framework
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
**Examples:**
|
|
535
|
-
- `SufficientBalanceGuard` → `enforce_sufficient_balance!` / `check_sufficient_balance?`
|
|
536
|
-
- `PresenceGuard` → `enforce_presence!` / `check_presence?`
|
|
537
|
-
- `AdminRoleGuard` → `enforce_admin_role!` / `check_admin_role?`
|
|
538
|
-
- `EnabledProductGuard` → `enforce_enabled_product!` / `check_enabled_product?`
|
|
539
|
-
|
|
540
|
-
**Remember:** Let the framework add the action verbs. Your job is to describe the condition! 🎯
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# @title Guides / 1. Common Patterns
|
|
2
|
-
|
|
3
|
-
# Common Patterns
|
|
4
|
-
|
|
5
|
-
Common architectural patterns for using Servus effectively.
|
|
6
|
-
|
|
7
|
-
## Parent-Child Services
|
|
8
|
-
|
|
9
|
-
When one service orchestrates multiple sub-operations, decide on transaction boundaries and error propagation.
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class Orders::Checkout::Service < Servus::Base
|
|
13
|
-
def call
|
|
14
|
-
ActiveRecord::Base.transaction do
|
|
15
|
-
# Create order
|
|
16
|
-
order_result = Orders::Create::Service.call(order_params)
|
|
17
|
-
return order_result unless order_result.success?
|
|
18
|
-
|
|
19
|
-
# Charge payment
|
|
20
|
-
payment_result = Payments::Charge::Service.call(
|
|
21
|
-
user_id: @user_id,
|
|
22
|
-
amount: order_result.data[:order].total
|
|
23
|
-
)
|
|
24
|
-
return payment_result unless payment_result.success?
|
|
25
|
-
|
|
26
|
-
# Update inventory
|
|
27
|
-
inventory_result = Inventory::Reserve::Service.call(
|
|
28
|
-
order_id: order_result.data[:order].id
|
|
29
|
-
)
|
|
30
|
-
return inventory_result unless inventory_result.success?
|
|
31
|
-
|
|
32
|
-
success(order: order_result.data[:order])
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Use parent transaction when**: All children must succeed or all roll back (atomic operation)
|
|
39
|
-
|
|
40
|
-
**Use child transactions when**: Children can succeed independently (partial success acceptable)
|
|
41
|
-
|
|
42
|
-
## Async with Result Persistence
|
|
43
|
-
|
|
44
|
-
Store async results in database for later retrieval:
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
# Controller creates placeholder
|
|
48
|
-
report = Report.create!(user_id: user.id, status: 'pending')
|
|
49
|
-
GenerateReport::Service.call_async(report_id: report.id)
|
|
50
|
-
|
|
51
|
-
# Service updates record
|
|
52
|
-
class GenerateReport::Service < Servus::Base
|
|
53
|
-
def call
|
|
54
|
-
report = Report.find(@report_id)
|
|
55
|
-
data = generate_report_data
|
|
56
|
-
|
|
57
|
-
report.update!(data: data, status: 'completed')
|
|
58
|
-
success(report: report)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Idempotent Services
|
|
64
|
-
|
|
65
|
-
Use database constraints to make services idempotent:
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
class Users::Create::Service < Servus::Base
|
|
69
|
-
def call
|
|
70
|
-
# Unique constraint on email prevents duplicates
|
|
71
|
-
user = User.create!(email: @email, name: @name)
|
|
72
|
-
success(user: user)
|
|
73
|
-
rescue ActiveRecord::RecordNotUnique
|
|
74
|
-
user = User.find_by!(email: @email)
|
|
75
|
-
success(user: user) # Return existing user, not error
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Or check for existing resources explicitly:
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
def call
|
|
84
|
-
existing = User.find_by(email: @email)
|
|
85
|
-
return success(user: existing) if existing
|
|
86
|
-
|
|
87
|
-
user = User.create!(email: @email, name: @name)
|
|
88
|
-
success(user: user)
|
|
89
|
-
end
|
|
90
|
-
```
|