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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/servus/event_handler/event_handler_generator.rb +1 -1
  3. data/lib/generators/servus/guard/guard_generator.rb +1 -1
  4. data/lib/generators/servus/guard/templates/guard.rb.erb +5 -3
  5. data/lib/generators/servus/service/service_generator.rb +1 -1
  6. data/lib/servus/base.rb +46 -3
  7. data/lib/servus/config.rb +71 -3
  8. data/lib/servus/events/bus.rb +29 -0
  9. data/lib/servus/events/emitter.rb +15 -0
  10. data/lib/servus/guard.rb +7 -6
  11. data/lib/servus/guards/falsey_guard.rb +3 -3
  12. data/lib/servus/guards/presence_guard.rb +4 -4
  13. data/lib/servus/guards/state_guard.rb +4 -5
  14. data/lib/servus/guards/truthy_guard.rb +3 -3
  15. data/lib/servus/helpers/controller_helpers.rb +40 -0
  16. data/lib/servus/support/errors.rb +16 -0
  17. data/lib/servus/support/lockdown.rb +94 -0
  18. data/lib/servus/support/logger.rb +16 -0
  19. data/lib/servus/support/validator.rb +65 -34
  20. data/lib/servus/testing/example_builders.rb +52 -0
  21. data/lib/servus/testing/matchers.rb +99 -0
  22. data/lib/servus/version.rb +1 -1
  23. data/lib/servus.rb +1 -0
  24. metadata +7 -111
  25. data/.claude/commands/check-docs.md +0 -1
  26. data/.claude/commands/consistency-check.md +0 -1
  27. data/.claude/commands/fine-tooth-comb.md +0 -1
  28. data/.claude/commands/red-green-refactor.md +0 -5
  29. data/.claude/settings.json +0 -24
  30. data/.rspec +0 -3
  31. data/.rubocop.yml +0 -27
  32. data/.yardopts +0 -6
  33. data/CHANGELOG.md +0 -169
  34. data/CLAUDE.md +0 -10
  35. data/IDEAS.md +0 -5
  36. data/LICENSE.txt +0 -21
  37. data/READme.md +0 -856
  38. data/Rakefile +0 -45
  39. data/docs/core/1_overview.md +0 -81
  40. data/docs/core/2_architecture.md +0 -120
  41. data/docs/core/3_service_objects.md +0 -154
  42. data/docs/features/1_schema_validation.md +0 -161
  43. data/docs/features/2_error_handling.md +0 -129
  44. data/docs/features/3_async_execution.md +0 -81
  45. data/docs/features/4_logging.md +0 -64
  46. data/docs/features/5_event_bus.md +0 -244
  47. data/docs/features/6_guards.md +0 -356
  48. data/docs/features/7_lazy_resolvers.md +0 -238
  49. data/docs/features/guards_naming_convention.md +0 -540
  50. data/docs/guides/1_common_patterns.md +0 -90
  51. data/docs/guides/2_migration_guide.md +0 -225
  52. data/docs/integration/1_configuration.md +0 -154
  53. data/docs/integration/2_testing.md +0 -304
  54. data/docs/integration/3_rails_integration.md +0 -99
  55. data/docs/yard/Servus/Base.html +0 -1645
  56. data/docs/yard/Servus/Config.html +0 -582
  57. data/docs/yard/Servus/Extensions/Async/Call.html +0 -400
  58. data/docs/yard/Servus/Extensions/Async/Errors/AsyncError.html +0 -140
  59. data/docs/yard/Servus/Extensions/Async/Errors/JobEnqueueError.html +0 -154
  60. data/docs/yard/Servus/Extensions/Async/Errors/ServiceNotFoundError.html +0 -154
  61. data/docs/yard/Servus/Extensions/Async/Errors.html +0 -128
  62. data/docs/yard/Servus/Extensions/Async/Ext.html +0 -119
  63. data/docs/yard/Servus/Extensions/Async/Job.html +0 -310
  64. data/docs/yard/Servus/Extensions/Async.html +0 -141
  65. data/docs/yard/Servus/Extensions.html +0 -117
  66. data/docs/yard/Servus/Generators/ServiceGenerator.html +0 -261
  67. data/docs/yard/Servus/Generators.html +0 -115
  68. data/docs/yard/Servus/Helpers/ControllerHelpers.html +0 -457
  69. data/docs/yard/Servus/Helpers.html +0 -115
  70. data/docs/yard/Servus/Railtie.html +0 -134
  71. data/docs/yard/Servus/Support/Errors/AuthenticationError.html +0 -287
  72. data/docs/yard/Servus/Support/Errors/BadRequestError.html +0 -283
  73. data/docs/yard/Servus/Support/Errors/ForbiddenError.html +0 -284
  74. data/docs/yard/Servus/Support/Errors/InternalServerError.html +0 -283
  75. data/docs/yard/Servus/Support/Errors/NotFoundError.html +0 -284
  76. data/docs/yard/Servus/Support/Errors/ServiceError.html +0 -489
  77. data/docs/yard/Servus/Support/Errors/ServiceUnavailableError.html +0 -290
  78. data/docs/yard/Servus/Support/Errors/UnauthorizedError.html +0 -200
  79. data/docs/yard/Servus/Support/Errors/UnprocessableEntityError.html +0 -288
  80. data/docs/yard/Servus/Support/Errors/ValidationError.html +0 -200
  81. data/docs/yard/Servus/Support/Errors.html +0 -140
  82. data/docs/yard/Servus/Support/Logger.html +0 -856
  83. data/docs/yard/Servus/Support/Rescuer/BlockContext.html +0 -585
  84. data/docs/yard/Servus/Support/Rescuer/CallOverride.html +0 -257
  85. data/docs/yard/Servus/Support/Rescuer/ClassMethods.html +0 -343
  86. data/docs/yard/Servus/Support/Rescuer.html +0 -267
  87. data/docs/yard/Servus/Support/Response.html +0 -574
  88. data/docs/yard/Servus/Support/Validator.html +0 -1150
  89. data/docs/yard/Servus/Support.html +0 -119
  90. data/docs/yard/Servus/Testing/ExampleBuilders.html +0 -523
  91. data/docs/yard/Servus/Testing/ExampleExtractor.html +0 -578
  92. data/docs/yard/Servus/Testing.html +0 -142
  93. data/docs/yard/Servus.html +0 -343
  94. data/docs/yard/_index.html +0 -535
  95. data/docs/yard/class_list.html +0 -54
  96. data/docs/yard/css/common.css +0 -1
  97. data/docs/yard/css/full_list.css +0 -58
  98. data/docs/yard/css/style.css +0 -503
  99. data/docs/yard/file.1_common_patterns.html +0 -154
  100. data/docs/yard/file.1_configuration.html +0 -115
  101. data/docs/yard/file.1_overview.html +0 -142
  102. data/docs/yard/file.1_schema_validation.html +0 -188
  103. data/docs/yard/file.2_architecture.html +0 -157
  104. data/docs/yard/file.2_error_handling.html +0 -190
  105. data/docs/yard/file.2_migration_guide.html +0 -242
  106. data/docs/yard/file.2_testing.html +0 -227
  107. data/docs/yard/file.3_async_execution.html +0 -145
  108. data/docs/yard/file.3_rails_integration.html +0 -160
  109. data/docs/yard/file.3_service_objects.html +0 -191
  110. data/docs/yard/file.4_logging.html +0 -135
  111. data/docs/yard/file.ErrorHandling.html +0 -190
  112. data/docs/yard/file.READme.html +0 -674
  113. data/docs/yard/file.architecture.html +0 -157
  114. data/docs/yard/file.async_execution.html +0 -145
  115. data/docs/yard/file.common_patterns.html +0 -154
  116. data/docs/yard/file.configuration.html +0 -115
  117. data/docs/yard/file.error_handling.html +0 -190
  118. data/docs/yard/file.logging.html +0 -135
  119. data/docs/yard/file.migration_guide.html +0 -242
  120. data/docs/yard/file.overview.html +0 -142
  121. data/docs/yard/file.rails_integration.html +0 -160
  122. data/docs/yard/file.schema_validation.html +0 -188
  123. data/docs/yard/file.service_objects.html +0 -191
  124. data/docs/yard/file.testing.html +0 -227
  125. data/docs/yard/file_list.html +0 -119
  126. data/docs/yard/frames.html +0 -22
  127. data/docs/yard/index.html +0 -674
  128. data/docs/yard/js/app.js +0 -344
  129. data/docs/yard/js/full_list.js +0 -242
  130. data/docs/yard/js/jquery.js +0 -4
  131. data/docs/yard/method_list.html +0 -542
  132. 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
- ```