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,66 @@
1
+ # Helper Anti-Patterns
2
+
3
+ Helpers doing work that belongs in templates or components.
4
+
5
+ ## HTML Construction in Helpers
6
+
7
+ **Problem:** Helpers building HTML programmatically instead of providing logic for templates.
8
+
9
+ ```ruby
10
+ # BAD: Helper constructs HTML
11
+ module MessagesHelper
12
+ def message_tag(message, &)
13
+ tag.div id: dom_id(message),
14
+ class: "message #{"message--emoji" if message.plain_text_body.all_emoji?}",
15
+ data: {
16
+ controller: "reply",
17
+ user_id: message.creator_id,
18
+ message_id: message.id,
19
+ # ... many more data attributes
20
+ }, &
21
+ end
22
+ end
23
+ ```
24
+
25
+ **Issues:**
26
+ - HTML structure hidden in Ruby code, harder to read and modify
27
+ - No template preview, harder to collaborate with designers
28
+ - Logic and markup tightly coupled
29
+ - Testing requires rendering, not unit testable
30
+ - Misses ViewComponent benefits (sidecar assets, previews, slots)
31
+
32
+ **Signal:** Heavy use of `tag.div`, `tag.button`, `tag.span` or complex `content_tag` chains.
33
+
34
+ **Fix:** Extract to ViewComponent with template.
35
+
36
+ ```ruby
37
+ # GOOD: ViewComponent with template
38
+ class MessageComponent < ViewComponent::Base
39
+ def initialize(message:)
40
+ @message = message
41
+ end
42
+
43
+ def emoji_only?
44
+ @message.plain_text_body.all_emoji?
45
+ end
46
+
47
+ def stimulus_data
48
+ {
49
+ controller: "reply",
50
+ user_id: @message.creator_id,
51
+ message_id: @message.id
52
+ }
53
+ end
54
+ end
55
+ ```
56
+
57
+ ```erb
58
+ <%# app/components/message_component.html.erb %>
59
+ <div id="<%= dom_id(@message) %>"
60
+ class="message <%= "message--emoji" if emoji_only? %>"
61
+ data="<%= stimulus_data.to_json %>">
62
+ <%= content %>
63
+ </div>
64
+ ```
65
+
66
+ **Rule of thumb:** If a helper method has more than 2-3 `tag.*` calls or builds nested HTML structure, extract to ViewComponent.
@@ -0,0 +1,71 @@
1
+ # Job Anti-Patterns
2
+
3
+ Background-job classes that add boilerplate without value.
4
+
5
+ ## Anemic Jobs
6
+
7
+ **Problem:** Job classes that just delegate to a single model method.
8
+
9
+ ```ruby
10
+ # BAD: Job is just a wrapper
11
+ class NotifyRecipientsJob < ApplicationJob
12
+ discard_on ActiveJob::DeserializationError
13
+
14
+ def perform(notifiable)
15
+ notifiable.notify_recipients
16
+ end
17
+ end
18
+
19
+ # BAD: Model method just to enqueue job
20
+ class Post < ApplicationRecord
21
+ def notify_recipients_later
22
+ NotifyRecipientsJob.perform_later(self)
23
+ end
24
+ end
25
+ ```
26
+
27
+ **Issues:**
28
+ - Boilerplate job files cluttering `app/jobs`
29
+ - Two places to maintain (job + model method)
30
+ - Job class adds no value beyond async execution
31
+ - Makes the jobs folder noisy, hiding complex jobs that need attention
32
+
33
+ **Signal:** Job's `perform` method is a single line calling a method on the argument.
34
+
35
+ **Fix:** Use the [active_job-performs](https://github.com/kaspth/active_job-performs) gem.
36
+
37
+ ```ruby
38
+ # GOOD: Short form (no job options needed)
39
+ class Post < ApplicationRecord
40
+ performs def notify_recipients
41
+ # Notification logic
42
+ end
43
+ end
44
+
45
+ # Usage
46
+ post.notify_recipients_later
47
+ ```
48
+
49
+ This generates:
50
+ - `Post::NotifyRecipientsJob` automatically
51
+ - `notify_recipients_later` instance method
52
+ - `notify_recipients_later_bulk` class method (Rails 7.1+)
53
+
54
+ **With options:**
55
+ ```ruby
56
+ class Post < ApplicationRecord
57
+ performs :notify_recipients,
58
+ queue_as: :notifications,
59
+ discard_on: ActiveRecord::RecordNotFound
60
+
61
+ def notify_recipients
62
+ # ...
63
+ end
64
+ end
65
+ ```
66
+
67
+ **When to keep separate job class:**
68
+ - Job has complex logic beyond single method call
69
+ - Job processes multiple records with custom batching
70
+ - Job needs extensive retry/error handling configuration
71
+ - Job is triggered from multiple unrelated models
@@ -0,0 +1,176 @@
1
+ # Layer Violations
2
+
3
+ Cross-layer dependencies that break unidirectional data flow.
4
+
5
+ ## Contents
6
+
7
+ - Current Attributes in Models
8
+ - Request Objects in Services
9
+ - Notifications in Models
10
+ - Business Logic in Controllers
11
+
12
+ ## Current Attributes in Models
13
+
14
+ **Problem:** Models depend on presentation-layer context.
15
+
16
+ ```ruby
17
+ # BAD
18
+ class Post < ApplicationRecord
19
+ def destroy
20
+ self.deleted_by = Current.user # Hidden dependency!
21
+ super
22
+ end
23
+ end
24
+ ```
25
+
26
+ **Issues:**
27
+ - Background jobs lose Current context (silent bugs)
28
+ - Callbacks can overwrite Current mid-iteration
29
+ - Hidden dependency makes testing harder
30
+ - Violates no-reverse-dependencies rule
31
+
32
+ **Fix:** Use explicit parameters.
33
+
34
+ ```ruby
35
+ # GOOD
36
+ class Post < ApplicationRecord
37
+ def destroy_by(user:)
38
+ self.deleted_by = user
39
+ destroy
40
+ end
41
+ end
42
+ ```
43
+
44
+ ## Request Objects in Services
45
+
46
+ **Problem:** Application layer depends on presentation layer.
47
+
48
+ ```ruby
49
+ # BAD
50
+ class HandleEventService
51
+ param :request
52
+
53
+ def call
54
+ event_type = request.headers["X-Event-Type"]
55
+ payload = JSON.parse(request.body.read)
56
+ # ...
57
+ end
58
+ end
59
+ ```
60
+
61
+ **Fix:** Extract value object in controller, pass to service.
62
+
63
+ ```ruby
64
+ # GOOD
65
+ class GithubCallbacksController < ApplicationController
66
+ def create
67
+ event = GithubEvent.from_request(request)
68
+ HandleEventService.call(event:)
69
+ end
70
+ end
71
+
72
+ class HandleEventService
73
+ param :event # Value object, not request
74
+
75
+ def call
76
+ # Work with clean domain object
77
+ end
78
+ end
79
+ ```
80
+
81
+ ## Notifications in Models
82
+
83
+ **Problem:** Model triggers notifications, crossing into application layer.
84
+
85
+ ```ruby
86
+ # BAD
87
+ class License < ApplicationRecord
88
+ def prolong
89
+ update!(status: :active, expires_at: 1.year.from_now)
90
+ LicenseDelivery.with(license: self).purchased.deliver_later
91
+ end
92
+ end
93
+ ```
94
+
95
+ **Issues:**
96
+ - Domain layer depends on application layer (reverse dependency)
97
+ - Model has side effects beyond state management
98
+ - Harder to test model in isolation
99
+ - Notification may fire unexpectedly from different call sites
100
+
101
+ **Fix:** Trace the call chain, move notification to existing orchestrator.
102
+
103
+ ```ruby
104
+ # GOOD: Service handles side effects
105
+ class StripeEventManager
106
+ def handle_invoice_paid(invoice)
107
+ # ... find license, create payment record ...
108
+ license.prolong
109
+ LicenseDelivery.with(license:).purchased.deliver_later
110
+ end
111
+ end
112
+
113
+ class License < ApplicationRecord
114
+ def prolong
115
+ update!(status: :active, expires_at: 1.year.from_now)
116
+ end
117
+ end
118
+ ```
119
+
120
+ **Resolution process:**
121
+ 1. Find the caller (controller, service, job)
122
+ 2. If orchestrator exists → move notification there
123
+ 3. If no orchestrator → suggest service/form/controller based on context
124
+
125
+ ## Business Logic in Controllers
126
+
127
+ **Problem:** Presentation layer doing domain work.
128
+
129
+ ```ruby
130
+ # BAD
131
+ class OrdersController < ApplicationController
132
+ def create
133
+ @order = Order.new(order_params)
134
+ @order.total = @order.items.sum { |i| i.price * i.quantity }
135
+ @order.total *= 0.9 if @order.customer.vip?
136
+ @order.total += calculate_shipping(@order)
137
+
138
+ if @order.save
139
+ OrderMailer.confirmation(@order).deliver_later
140
+ redirect_to @order
141
+ else
142
+ render :new
143
+ end
144
+ end
145
+ end
146
+ ```
147
+
148
+ **Fix:** Move domain logic to model, orchestration to service if needed.
149
+
150
+ ```ruby
151
+ # GOOD
152
+ class Order < ApplicationRecord
153
+ before_validation :calculate_total
154
+
155
+ private
156
+
157
+ def calculate_total
158
+ self.total = items.sum(&:subtotal)
159
+ self.total *= 0.9 if customer.vip?
160
+ self.total += shipping_cost
161
+ end
162
+ end
163
+
164
+ class OrdersController < ApplicationController
165
+ def create
166
+ @order = Order.new(order_params)
167
+
168
+ if @order.save
169
+ OrderMailer.confirmation(@order).deliver_later
170
+ redirect_to @order
171
+ else
172
+ render :new
173
+ end
174
+ end
175
+ end
176
+ ```
@@ -0,0 +1,131 @@
1
+ # Service Object Anti-Patterns
2
+
3
+ Common mistakes when introducing service objects.
4
+
5
+ ## Contents
6
+
7
+ - Anemic Models
8
+ - Bag of Random Objects
9
+ - Premature Abstraction
10
+
11
+ ## Anemic Models
12
+
13
+ **Problem:** All logic moved to services, models become data containers.
14
+
15
+ ```ruby
16
+ # BAD - Anemic model
17
+ class Order < ApplicationRecord
18
+ # Just associations and validations, no behavior
19
+ end
20
+
21
+ class CalculateOrderTotalService
22
+ def call(order)
23
+ total = order.items.sum { |i| i.price * i.quantity }
24
+ total *= 0.9 if order.customer.vip?
25
+ order.update!(total:)
26
+ end
27
+ end
28
+
29
+ class ApplyDiscountService
30
+ def call(order, code)
31
+ discount = Discount.find_by(code:)
32
+ order.update!(discount_amount: discount.amount)
33
+ end
34
+ end
35
+ ```
36
+
37
+ **Fix:** Keep domain logic in models. Services orchestrate, models know their business rules.
38
+
39
+ ```ruby
40
+ # GOOD
41
+ class Order < ApplicationRecord
42
+ def calculate_total
43
+ self.total = items.sum(&:subtotal)
44
+ apply_vip_discount if customer.vip?
45
+ end
46
+
47
+ def apply_discount(code)
48
+ discount = Discount.find_by(code:)
49
+ self.discount_amount = discount.amount
50
+ end
51
+ end
52
+ ```
53
+
54
+ ## Bag of Random Objects
55
+
56
+ **Problem:** No conventions, each service is unique.
57
+
58
+ ```ruby
59
+ # BAD - No consistency
60
+ class UserRegistration
61
+ def perform(attrs)
62
+ # returns user or nil
63
+ end
64
+ end
65
+
66
+ class OrderProcessor
67
+ def self.process!(order_id)
68
+ # raises on failure
69
+ end
70
+ end
71
+
72
+ class SendNewsletterJob
73
+ def run(newsletter, subscribers)
74
+ # returns count
75
+ end
76
+ end
77
+ ```
78
+
79
+ **Fix:** Establish conventions.
80
+
81
+ ```ruby
82
+ # GOOD - Consistent interface
83
+ class ApplicationService
84
+ extend Dry::Initializer
85
+ def self.call(...) = new(...).call
86
+ end
87
+
88
+ class RegisterUserService < ApplicationService
89
+ param :attrs
90
+ def call
91
+ # Returns result object
92
+ end
93
+ end
94
+
95
+ class ProcessOrderService < ApplicationService
96
+ param :order_id
97
+ def call
98
+ # Returns result object
99
+ end
100
+ end
101
+ ```
102
+
103
+ ## Premature Abstraction
104
+
105
+ **Problem:** Creating abstractions before patterns emerge.
106
+
107
+ ```ruby
108
+ # BAD - Over-engineered from day one
109
+ class BaseCommand
110
+ include CommandPattern
111
+ include ResultMonad
112
+ include TransactionWrapper
113
+ end
114
+
115
+ class CreateUserCommand < BaseCommand
116
+ # Complex infrastructure for simple operation
117
+ end
118
+ ```
119
+
120
+ **Fix:** Wait for patterns to emerge. Start simple.
121
+
122
+ ```ruby
123
+ # GOOD - Simple first
124
+ class CreateUserService
125
+ def self.call(params)
126
+ User.create!(params)
127
+ end
128
+ end
129
+
130
+ # Extract patterns AFTER you see repetition
131
+ ```
@@ -0,0 +1,37 @@
1
+ # Testing Anti-Patterns
2
+
3
+ Tests that verify the wrong layer's responsibilities.
4
+
5
+ ## Testing Wrong Layer
6
+
7
+ **Problem:** Controller tests verify business logic.
8
+
9
+ ```ruby
10
+ # BAD
11
+ describe OrdersController do
12
+ it "applies VIP discount" do
13
+ post :create, params: { items: [...] }
14
+ expect(Order.last.total).to eq(90) # Testing domain logic!
15
+ end
16
+ end
17
+ ```
18
+
19
+ **Fix:** Test business logic in model specs.
20
+
21
+ ```ruby
22
+ # GOOD
23
+ describe Order do
24
+ it "applies VIP discount" do
25
+ order = build(:order, customer: vip_customer)
26
+ order.calculate_total
27
+ expect(order.total).to eq(90)
28
+ end
29
+ end
30
+
31
+ describe OrdersController do
32
+ it "creates order and redirects" do
33
+ post :create, params: { items: [...] }
34
+ expect(response).to redirect_to(Order.last)
35
+ end
36
+ end
37
+ ```
@@ -0,0 +1,204 @@
1
+ # Architecture Layers
2
+
3
+ ## Contents
4
+
5
+ - The Four Layers (Presentation, Application, Domain, Infrastructure)
6
+ - The Four Layering Rules
7
+ - Mapping Rails Components to Layers
8
+ - Applying Layered Architecture in Practice
9
+ - Common Layering Mistakes
10
+
11
+ ## The Four Layers (Presentation, Application, Domain, Infrastructure)
12
+
13
+ Rails applications are organized into four architecture layers with unidirectional data flow:
14
+
15
+ | Layer | Responsibility | Rails Examples |
16
+ |-------|----------------|----------------|
17
+ | **Presentation** | Handle user interactions, present information | Controllers, Views, Channels, Mailers |
18
+ | **Application** | Organize domain objects for use cases | Service objects, Form objects, Policy objects |
19
+ | **Domain** | Entities, rules, invariants, application state | Models, Value objects, Domain events |
20
+ | **Infrastructure** | Supporting technologies | Active Record, API clients, File storage |
21
+
22
+ ```
23
+ Presentation → Application → Domain → Infrastructure
24
+ ```
25
+
26
+ ## The Four Layering Rules
27
+
28
+ ### Rule 1: Unidirectional Data Flow
29
+
30
+ Data flows from top to bottom only. Arrows in the architecture always point downward.
31
+
32
+ ### Rule 2: No Reverse Dependencies
33
+
34
+ Lower layers must not depend on higher layers. A domain object should never depend on a controller or request object.
35
+
36
+ **Violations:**
37
+
38
+ ```ruby
39
+ # BAD: Service (Application) depends on request (Presentation)
40
+ class HandleEventService
41
+ param :request # Reverse dependency!
42
+ end
43
+
44
+ # BAD: Model (Domain) depends on Current (Presentation context)
45
+ class Post < ApplicationRecord
46
+ def destroy
47
+ self.deleted_by = Current.user # Hidden dependency!
48
+ super
49
+ end
50
+ end
51
+ ```
52
+
53
+ **Correct:**
54
+
55
+ ```ruby
56
+ # GOOD: Service accepts domain objects only
57
+ class HandleEventService
58
+ param :event # Value object, not request
59
+
60
+ def call
61
+ user = User.find_by(gh_id: event.user_id)
62
+ # ...
63
+ end
64
+ end
65
+
66
+ # GOOD: Model method accepts explicit parameters
67
+ class Post < ApplicationRecord
68
+ def destroy_by(user:)
69
+ self.deleted_by = user
70
+ destroy
71
+ end
72
+ end
73
+ ```
74
+
75
+ ### Rule 3: Abstraction Boundaries
76
+
77
+ Every abstraction layer must belong to a single architecture layer. An abstraction cannot span multiple architecture layers.
78
+
79
+ **Evaluating abstractions:**
80
+ - Does this object depend on objects from a higher layer? → Extract or refactor
81
+ - Does this object's responsibility match its architecture layer? → Move if not
82
+
83
+ ### Rule 4: Minimize Inter-Layer Connections
84
+
85
+ Fewer connections = looser coupling = better testability and reusability.
86
+
87
+ **Good layering:**
88
+ ```ruby
89
+ # Controller (Presentation) → Service (Application) → Model (Domain)
90
+ class PostsController
91
+ def publish
92
+ PublishPostService.call(post_id: params[:id], user: current_user)
93
+ redirect_to posts_path
94
+ end
95
+ end
96
+
97
+ class PublishPostService
98
+ def call
99
+ post = Post.find(post_id)
100
+ post.publish!(by: user) # Domain logic stays in model
101
+ end
102
+ end
103
+ ```
104
+
105
+ **Caveat — Architecture Sinkhole:** If you reduce connections to minimum (each layer only talks to adjacent layer), you may create objects that just proxy data through layers with no modification.
106
+
107
+ ```ruby
108
+ # BAD: Service does nothing but proxy
109
+ class FindPostService
110
+ def call(id)
111
+ Post.find(id) # No value added, just pass-through
112
+ end
113
+ end
114
+ ```
115
+
116
+ ## Mapping Rails Components to Layers
117
+
118
+ ### Presentation Layer
119
+
120
+ **Purpose:** Handle user interactions, present information
121
+
122
+ **Includes:**
123
+ - Controllers (HTTP request/response)
124
+ - Views (HTML rendering)
125
+ - Channels (WebSocket connections)
126
+ - Mailers (email composition)
127
+ - API serializers
128
+ - Form objects (user input handling)
129
+ - Filter objects (request parameter transformation)
130
+ - Presenters (view-specific logic)
131
+
132
+ **Primary concerns:**
133
+ - Request parsing and validation
134
+ - Authentication
135
+ - Response formatting
136
+ - User interface logic
137
+
138
+ ### Application Layer
139
+
140
+ **Purpose:** Organize domain objects for specific use cases
141
+
142
+ **Includes:**
143
+ - Service objects (business operations)
144
+ - Policy objects (authorization)
145
+ - Interactors/Commands
146
+
147
+ **Primary concerns:**
148
+ - Orchestrating domain objects
149
+ - Transaction boundaries
150
+ - Use-case specific logic
151
+
152
+ **Warning:** This layer is often overused. Don't strip all logic from models into services (anemic models anti-pattern).
153
+
154
+ ### Domain Layer
155
+
156
+ **Purpose:** Entities, rules, invariants, application state
157
+
158
+ **Includes:**
159
+ - Models (business entities)
160
+ - Value objects (immutable concepts)
161
+ - Domain events
162
+ - Query objects (data retrieval logic)
163
+ - Concerns (shared behaviors)
164
+
165
+ **Primary concerns:**
166
+ - Business rules and invariants
167
+ - Entity relationships
168
+ - Data transformations
169
+ - Domain-specific calculations
170
+
171
+ ### Infrastructure Layer
172
+
173
+ **Purpose:** Supporting technologies
174
+
175
+ **Includes:**
176
+ - Active Record (database access)
177
+ - API clients (external services)
178
+ - File storage adapters
179
+ - Message queue adapters
180
+ - Cache implementations
181
+
182
+ **Primary concerns:**
183
+ - Persistence
184
+ - External communication
185
+ - Technical implementations
186
+
187
+ ## Applying Layered Architecture in Practice
188
+
189
+ When designing or refactoring code:
190
+
191
+ 1. **Identify the architecture layer** the code belongs to
192
+ 2. **Check dependencies** — does it depend on higher layers?
193
+ 3. **Apply specification test** — do tests verify appropriate responsibilities?
194
+ 4. **Extract if needed** — move code to the correct layer
195
+
196
+ ## Common Layering Mistakes
197
+
198
+ | Mistake | Problem | Solution |
199
+ |---------|---------|----------|
200
+ | Current in models | Hidden dependency on presentation context | Pass as explicit parameter |
201
+ | Request in services | Service depends on HTTP layer | Extract value object from request |
202
+ | Mailer in callbacks | Model triggers presentation-layer code | Use events or move to controller |
203
+ | SQL in controllers | Presentation doing infrastructure work | Use model scopes or query objects |
204
+ | Business logic in views | Presentation doing domain work | Use presenters or model methods |