rails-hyperdrive-layered-rails 0.1.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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +30 -0
  3. data/README.md +89 -0
  4. data/UPSTREAM +4 -0
  5. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/SKILL.md.erb +315 -0
  6. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/authorization-to-policy.md +79 -0
  7. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/callbacks-to-service.md +72 -0
  8. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/complex-input-to-form-object.md +119 -0
  9. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/current-from-model.md +63 -0
  10. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/god-object-decomposition.md +81 -0
  11. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/implicit-to-explicit-state-machine.md +77 -0
  12. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/query-to-query-object.md +63 -0
  13. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/examples/view-logic-to-presenter.md +60 -0
  14. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/callbacks.md +114 -0
  15. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/concerns.md +92 -0
  16. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/helpers.md +66 -0
  17. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/jobs.md +71 -0
  18. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/layer-violations.md +176 -0
  19. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/service-objects.md +131 -0
  20. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/anti-patterns/testing.md +37 -0
  21. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/core/architecture-layers.md +204 -0
  22. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/core/extraction-signals.md +314 -0
  23. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/core/specification-test.md +229 -0
  24. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/action-policy.md +437 -0
  25. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/active-agent.md +275 -0
  26. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/active-delivery.md +221 -0
  27. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/active-job-performs.md +171 -0
  28. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/alba.md +257 -0
  29. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/anyway-config.md +212 -0
  30. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/rubanok.md +246 -0
  31. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/view-component.md +223 -0
  32. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/gems/workflow.md +305 -0
  33. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/collaborator-objects.md +209 -0
  34. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/concerns.md +381 -0
  35. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/filter-objects.md +222 -0
  36. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/form-objects.md +267 -0
  37. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/policy-objects.md +298 -0
  38. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/presenters.md +257 -0
  39. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/query-objects.md +208 -0
  40. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/repositories.md +382 -0
  41. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/serializers.md +256 -0
  42. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/service-objects.md +170 -0
  43. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/state-machines.md +368 -0
  44. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/patterns/value-objects.md +267 -0
  45. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/ai-integration.md +372 -0
  46. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/authorization.md +398 -0
  47. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/callbacks.md +336 -0
  48. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/configuration.md +383 -0
  49. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/current-attributes.md +307 -0
  50. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/instrumentation.md +386 -0
  51. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/notifications.md +373 -0
  52. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/references/topics/view-components.md +454 -0
  53. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/analyze-callbacks.md +255 -0
  54. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/analyze-gods.md +279 -0
  55. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/analyze-services.md +1444 -0
  56. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/analyze.md +376 -0
  57. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/plan.md +249 -0
  58. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/review.md +473 -0
  59. data/lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/workflows/spec-test.md +282 -0
  60. data/lib/rails-hyperdrive-layered-rails/version.rb +3 -0
  61. data/lib/rails-hyperdrive-layered-rails.rb +7 -0
  62. metadata +106 -0
@@ -0,0 +1,314 @@
1
+ # Extraction Signals
2
+
3
+ How to identify code that should be extracted to a different layer or abstraction.
4
+
5
+ ## Contents
6
+
7
+ - Callback Scoring System
8
+ - God Object Identification
9
+ - Concern Health Check
10
+ - Service Object Signals
11
+ - Controller Fat Signals
12
+ - Quick Reference
13
+ - Static Analysis Tools
14
+
15
+ ## Callback Scoring System
16
+
17
+ Rate model callbacks to identify extraction candidates:
18
+
19
+ | Score | Type | Description | Action |
20
+ |-------|------|-------------|--------|
21
+ | 5/5 | **Transformer** | Computes/defaults required values | Keep in model |
22
+ | 4/5 | **Normalizer** | Sanitizes input data | Keep (prefer `.normalizes` API) |
23
+ | 4/5 | **Utility** | Counter caches, cache busting | Keep in model |
24
+ | 2/5 | **Observer** | Side effects after commit | Review case-by-case |
25
+ | 1/5 | **Operation** | Business process steps | Extract immediately |
26
+
27
+ ### Transformer Callbacks (Keep)
28
+
29
+ Compute or default required attribute values:
30
+
31
+ ```ruby
32
+ class Post < ApplicationRecord
33
+ before_validation :compute_shortname, on: :create
34
+ before_save :set_word_count, if: :content_changed?
35
+
36
+ private
37
+
38
+ def compute_shortname
39
+ self.short_name ||= title.parameterize
40
+ end
41
+
42
+ def set_word_count
43
+ self.word_count = content.split(/\s+/).size
44
+ end
45
+ end
46
+ ```
47
+
48
+ ### Normalizer Callbacks (Keep)
49
+
50
+ Sanitize user input. Prefer Rails 7.1+ `.normalizes` API:
51
+
52
+ ```ruby
53
+ class Post < ApplicationRecord
54
+ normalizes :title, with: -> { _1.strip }
55
+ normalizes :content, with: -> { _1.squish }
56
+ end
57
+ ```
58
+
59
+ ### Utility Callbacks (Keep)
60
+
61
+ Framework-level utilities like counter caches:
62
+
63
+ ```ruby
64
+ class Comment < ApplicationRecord
65
+ belongs_to :post, touch: true, counter_cache: true
66
+ end
67
+ ```
68
+
69
+ ### Operation Callbacks (Extract)
70
+
71
+ Signs of misplacement:
72
+ - Conditions (`unless: :admin?`)
73
+ - Collaboration with non-model objects (mailers, API clients)
74
+ - Remote peer communication
75
+
76
+ ```ruby
77
+ # BAD - Extract these
78
+ class User < ApplicationRecord
79
+ after_create :generate_initial_project, unless: :admin?
80
+ after_commit :send_welcome_email, on: :create
81
+ after_commit :sync_with_crm
82
+ after_commit :track_signup_analytics
83
+ end
84
+ ```
85
+
86
+ **Extraction options:**
87
+ 1. Move to controller
88
+ 2. Move to service object
89
+ 3. Use event-driven approach
90
+
91
+ ```ruby
92
+ # GOOD - Event-driven
93
+ class User < ApplicationRecord
94
+ after_commit on: :create do
95
+ UserCreatedEvent.publish(user: self)
96
+ end
97
+ end
98
+
99
+ # Subscribers handle side effects
100
+ class WelcomeEmailSubscriber
101
+ def user_created(event)
102
+ UserMailer.welcome(event.user).deliver_later
103
+ end
104
+ end
105
+ ```
106
+
107
+ ## God Object Identification
108
+
109
+ ### Churn × Complexity Metric
110
+
111
+ **Churn** = how often a file changes (indicates ongoing modifications)
112
+ **Complexity** = code complexity score (use Flog)
113
+
114
+ Files high in both are prime refactoring candidates.
115
+
116
+ ```bash
117
+ # Calculate churn
118
+ git log --format=oneline -- app/models/user.rb | wc -l
119
+
120
+ # Calculate complexity
121
+ flog -s app/models/user.rb
122
+
123
+ # Find intersection of top 10 by each
124
+ ```
125
+
126
+ ### Automated Tool
127
+
128
+ Use [attractor](https://github.com/julianrubisch/attractor):
129
+
130
+ ```bash
131
+ attractor report -p app/models
132
+ ```
133
+
134
+ ### Common God Object Names
135
+
136
+ Watch for these accumulating responsibilities:
137
+ - `User` / `Account`
138
+ - `Order` / `Transaction`
139
+ - `Project` / `Workspace`
140
+ - `Post` / `Article`
141
+
142
+ ### Decomposition Strategies
143
+
144
+ 1. **Extract concerns** for shared behaviors
145
+ 2. **Extract delegate objects** for complex operations
146
+ 3. **Extract value objects** for groups of related attributes
147
+ 4. **Create new models** for distinct concepts
148
+
149
+ ```ruby
150
+ # Before: God User model
151
+ class User < ApplicationRecord
152
+ # Authentication (20 methods)
153
+ # Profile (15 methods)
154
+ # Notifications (10 methods)
155
+ # Analytics (10 methods)
156
+ end
157
+
158
+ # After: Decomposed
159
+ class User < ApplicationRecord
160
+ include User::Authentication
161
+ has_one :profile
162
+ has_one :notification_preferences
163
+ end
164
+
165
+ class User::Authentication
166
+ # Authentication behavior
167
+ end
168
+
169
+ class Profile < ApplicationRecord
170
+ belongs_to :user
171
+ end
172
+
173
+ class NotificationPreferences < ApplicationRecord
174
+ belongs_to :user
175
+ end
176
+ ```
177
+
178
+ ## Concern Health Check
179
+
180
+ ### Good Concerns (Behavioral)
181
+
182
+ Can be tested in isolation, shared across models:
183
+
184
+ ```ruby
185
+ module Publishable
186
+ extend ActiveSupport::Concern
187
+
188
+ included do
189
+ scope :published, -> { where.not(published_at: nil) }
190
+ scope :draft, -> { where(published_at: nil) }
191
+ end
192
+
193
+ def published? = published_at.present?
194
+ def publish! = update!(published_at: Time.current)
195
+ end
196
+ ```
197
+
198
+ **Test:** Can you write specs for this concern without instantiating the host model?
199
+
200
+ ### Bad Concerns (Code-Slicing)
201
+
202
+ Groups code by Rails artifact type, not behavior:
203
+
204
+ ```ruby
205
+ # BAD - Just groups contact-related code
206
+ module Contactable
207
+ extend ActiveSupport::Concern
208
+
209
+ included do
210
+ validates :email, presence: true
211
+ validates :phone, format: { with: PHONE_REGEX }
212
+ before_save :normalize_phone
213
+ end
214
+
215
+ def full_contact_info
216
+ "#{email} / #{phone}"
217
+ end
218
+ end
219
+ ```
220
+
221
+ **Test:** If removing this concern breaks unrelated tests, it's code-slicing.
222
+
223
+ ### Overgrown Concerns
224
+
225
+ Signs a concern should be extracted:
226
+ - 50+ lines
227
+ - Multiple responsibilities
228
+ - Complex internal state
229
+
230
+ Extract to:
231
+ - **Delegate object** for operations
232
+ - **Value object** for attribute groups
233
+ - **Separate model** for distinct entity
234
+
235
+ ## Service Object Signals
236
+
237
+ ### When to Extract to Service
238
+
239
+ Extract from controller when you see:
240
+ - Multiple model operations in sequence
241
+ - Transaction spanning multiple models
242
+ - Complex error handling
243
+ - Reusable business operation
244
+
245
+ ### When NOT to Use Services
246
+
247
+ Don't extract:
248
+ - Single model operations (keep in model)
249
+ - Simple CRUD (let controller handle)
250
+ - Domain logic (belongs in model, not service)
251
+
252
+ ### Anemic Model Warning Signs
253
+
254
+ Your models might be anemic if:
255
+ - Services contain calculations that use only model data
256
+ - Models are pure data containers (associations + validations only)
257
+ - You have `CalculateXService` for model attributes
258
+ - Domain rules live in services, not models
259
+
260
+ ```ruby
261
+ # BAD - Anemic
262
+ class Order < ApplicationRecord
263
+ # Just associations and validations
264
+ end
265
+
266
+ class CalculateOrderTotalService
267
+ def call(order)
268
+ order.items.sum { |i| i.price * i.quantity }
269
+ end
270
+ end
271
+
272
+ # GOOD - Rich model
273
+ class Order < ApplicationRecord
274
+ def total
275
+ items.sum(&:subtotal)
276
+ end
277
+ end
278
+ ```
279
+
280
+ ## Controller Fat Signals
281
+
282
+ ### Extract When You See
283
+
284
+ - Business calculations (pricing, discounts)
285
+ - Multiple model updates
286
+ - Complex conditionals based on business rules
287
+ - External API calls
288
+ - More than 10-15 lines per action
289
+
290
+ ### Keep in Controller
291
+
292
+ - Parameter parsing
293
+ - Authentication/authorization
294
+ - Response formatting
295
+ - Simple model operations
296
+
297
+ ## Quick Reference
298
+
299
+ | Signal | Threshold | Action |
300
+ |--------|-----------|--------|
301
+ | Callback score | ≤ 2/5 | Extract to service/event |
302
+ | Model complexity | Flog > 100 | Decompose |
303
+ | Model churn | > 30 changes/year | Review for extraction |
304
+ | Concern size | > 50 lines | Extract to delegate |
305
+ | Controller action | > 15 lines | Extract to service |
306
+ | Service with domain logic | Any calculations | Move to model |
307
+
308
+ ## Static Analysis Tools
309
+
310
+ | Tool | Purpose | Command |
311
+ |------|---------|---------|
312
+ | [flog](https://github.com/seattlerb/flog) | Complexity scoring | `flog -s app/models/` |
313
+ | [attractor](https://github.com/julianrubisch/attractor) | Churn × complexity | `attractor report` |
314
+ | [callback_hell](https://github.com/evilmartians/callback_hell) | Callback audit | `bin/rails callback_hell:callbacks[User]` |
@@ -0,0 +1,229 @@
1
+ # The Specification Test
2
+
3
+ ## Contents
4
+
5
+ - The Specification Principle
6
+ - Quick Test-Outline Diagnostic
7
+ - Layer Responsibilities
8
+ - Example: Controller Specification
9
+ - Example: Service Specification
10
+ - Example: Model Specification
11
+ - Cost Consideration
12
+ - Detailed Code Audit (3-Step Process)
13
+ - Quick Reference
14
+
15
+ ## The Specification Principle
16
+
17
+ > If the specification of an object describes features beyond the primary responsibility of its abstraction layer, such features should be extracted into lower layers.
18
+
19
+ The specification test helps identify code that belongs in a different layer by examining what tests would verify.
20
+
21
+ ## Quick Test-Outline Diagnostic
22
+
23
+ 1. **Write test structure** (contexts/describes) without implementation
24
+ 2. **Examine what the tests verify**
25
+ 3. **Ask:** Does this test verify the object's primary responsibility?
26
+
27
+ If a test verifies something outside the layer's primary concern, that code should be extracted.
28
+
29
+ ## Layer Responsibilities
30
+
31
+ | Layer | Primary Responsibility | Tests Should Verify |
32
+ |-------|------------------------|---------------------|
33
+ | Presentation (Controller) | HTTP handling | Authentication, authorization, response codes, redirects |
34
+ | Application (Service) | Use-case orchestration | Correct domain objects called, transaction boundaries |
35
+ | Domain (Model) | Business rules | Validations, state transitions, calculations |
36
+ | Infrastructure | Technical implementation | Data persistence, API calls |
37
+
38
+ ## Example: Controller Specification
39
+
40
+ ```ruby
41
+ describe "/callbacks/github" do
42
+ context "when signature is missing" # ✓ Authentication (controller responsibility)
43
+ context "when signature is invalid" # ✓ Authentication (controller responsibility)
44
+ context "when event is pull_request" # ✗ Business logic (extract to lower layer)
45
+ context "when event is issue" # ✗ Business logic (extract to lower layer)
46
+ context "when user is not found" # ✗ Business logic (extract to lower layer)
47
+ end
48
+ ```
49
+
50
+ The business logic tests indicate code that should move to the application or domain layer.
51
+
52
+ **After extraction:**
53
+
54
+ ```ruby
55
+ # Controller spec - only HTTP concerns
56
+ describe "/callbacks/github" do
57
+ context "when signature is missing"
58
+ context "when signature is invalid"
59
+ context "when signature is valid"
60
+ end
61
+
62
+ # Service spec - business logic
63
+ describe HandleGithubEventService do
64
+ context "when event is pull_request"
65
+ context "when event is issue"
66
+ context "when user is not found"
67
+ end
68
+ ```
69
+
70
+ ## Example: Service Specification
71
+
72
+ ```ruby
73
+ describe ProcessOrderService do
74
+ context "when order is valid" # ✓ Orchestration
75
+ context "when payment fails" # ✓ Error handling
76
+ context "when inventory check fails" # ✓ Error handling
77
+ context "when discount > 50%" # ✗ Business rule (move to model)
78
+ context "when order total < minimum" # ✗ Business rule (move to model)
79
+ end
80
+ ```
81
+
82
+ Discount and minimum order rules are domain logic—they belong in the Order model.
83
+
84
+ ## Example: Model Specification
85
+
86
+ ```ruby
87
+ describe Order do
88
+ context "validations" # ✓ Business rules
89
+ context "#calculate_total" # ✓ Domain calculation
90
+ context "#apply_discount" # ✓ Domain logic
91
+ context "when sending confirmation" # ✗ Presentation concern (extract)
92
+ context "when syncing to warehouse" # ✗ Infrastructure concern (extract)
93
+ end
94
+ ```
95
+
96
+ Notification and external sync are not domain responsibilities.
97
+
98
+ ## Cost Consideration
99
+
100
+ Higher-layer tests are:
101
+ - **Harder to write** (more context setup)
102
+ - **Slower to execute** (HTTP requests, full stack)
103
+ - **More brittle** (depend on more components)
104
+
105
+ Moving logic to lower layers enables faster, simpler, more focused tests.
106
+
107
+ | Test Type | Speed | Setup Complexity | Brittleness |
108
+ |-----------|-------|------------------|-------------|
109
+ | Model/unit | Fast | Low | Low |
110
+ | Service | Medium | Medium | Medium |
111
+ | Controller/request | Slow | High | High |
112
+ | System/integration | Slowest | Highest | Highest |
113
+
114
+ ## Detailed Code Audit (3-Step Process)
115
+
116
+ ### Step 1: List Responsibilities
117
+
118
+ For the code you're examining, list every responsibility it handles.
119
+
120
+ Example for `OrdersController#create`:
121
+ - Parse order parameters
122
+ - Authenticate user
123
+ - Authorize order creation
124
+ - Validate inventory
125
+ - Calculate pricing
126
+ - Apply discounts
127
+ - Create order record
128
+ - Send confirmation email
129
+ - Sync to warehouse API
130
+ - Return JSON response
131
+
132
+ ### Step 2: Categorize by Layer
133
+
134
+ | Responsibility | Layer | Belongs in Controller? |
135
+ |----------------|-------|------------------------|
136
+ | Parse parameters | Presentation | ✓ Yes |
137
+ | Authenticate user | Presentation | ✓ Yes |
138
+ | Authorize creation | Application | ✓ Yes (or policy) |
139
+ | Validate inventory | Domain | ✗ No |
140
+ | Calculate pricing | Domain | ✗ No |
141
+ | Apply discounts | Domain | ✗ No |
142
+ | Create record | Domain | ✗ No |
143
+ | Send email | Presentation | ✗ No (not controller's job) |
144
+ | Sync to API | Infrastructure | ✗ No |
145
+ | Return JSON | Presentation | ✓ Yes |
146
+
147
+ ### Step 3: Extract
148
+
149
+ Move misplaced responsibilities to appropriate layers:
150
+
151
+ ```ruby
152
+ # Controller - only HTTP concerns
153
+ class OrdersController < ApplicationController
154
+ def create
155
+ result = CreateOrderService.call(
156
+ params: order_params,
157
+ user: current_user
158
+ )
159
+
160
+ if result.success?
161
+ render json: OrderSerializer.new(result.order)
162
+ else
163
+ render json: { errors: result.errors }, status: :unprocessable_entity
164
+ end
165
+ end
166
+ end
167
+
168
+ # Service - orchestration
169
+ class CreateOrderService
170
+ def call
171
+ order = Order.new(params)
172
+ order.customer = user
173
+
174
+ return failure(order.errors) unless order.valid?
175
+
176
+ order.save!
177
+ OrderMailer.confirmation(order).deliver_later
178
+ WarehouseSyncJob.perform_later(order.id)
179
+
180
+ success(order)
181
+ end
182
+ end
183
+
184
+ # Model - domain logic
185
+ class Order < ApplicationRecord
186
+ def valid?
187
+ validate_inventory
188
+ calculate_pricing
189
+ apply_discounts
190
+ super
191
+ end
192
+ end
193
+ ```
194
+
195
+ ## Quick Reference
196
+
197
+ **Controller should test:**
198
+ - HTTP status codes
199
+ - Redirects
200
+ - Authentication/authorization
201
+ - Parameter handling
202
+ - Response format
203
+
204
+ **Controller should NOT test:**
205
+ - Business rules
206
+ - Calculations
207
+ - State transitions
208
+ - External service behavior
209
+
210
+ **Service should test:**
211
+ - Correct objects orchestrated
212
+ - Transaction success/failure
213
+ - Error handling
214
+
215
+ **Service should NOT test:**
216
+ - Domain validation rules
217
+ - Business calculations
218
+ - HTTP concerns
219
+
220
+ **Model should test:**
221
+ - Validations
222
+ - Business rules
223
+ - Calculations
224
+ - State transitions
225
+
226
+ **Model should NOT test:**
227
+ - HTTP concerns
228
+ - Notification delivery
229
+ - External API calls