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