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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 89f911cbfd170fc326f1a6747b03a7029e95f8fa3e9105dd2af0e61589edc3d3
4
+ data.tar.gz: c2278d3155483e89d7178c918fc68a8803b3e2dbef06098cec0e1d625625b993
5
+ SHA512:
6
+ metadata.gz: a2b3271ab7ed1c586cade095b85f940ab933f098111587b7e3b535b9b40828a069d144c50033a73072eb6dc1c0b0aceb6ae9e41b3d756f143907d099cb030082
7
+ data.tar.gz: 9031faaf9753842be886ef071680aa5061f337a9cf8a17b78c589c3018e7c009e25a088de19936b4b526a0b9b3ecffc25ca050d3668e2fa65be36574c21a52f7
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Albert (packaging)
4
+ Copyright (c) Vladimir Dementyev (vendored skill content)
5
+
6
+ The skill content under
7
+ `lib/rails-hyperdrive-layered-rails/hyperdrive/skills/layered-rails/` is
8
+ vendored from https://github.com/palkan/skills (see the `UPSTREAM` file for the
9
+ exact ref). That project declares the MIT license in its README and
10
+ `.claude-plugin/marketplace.json`; it ships no LICENSE file of its own. The
11
+ content derives from Vladimir Dementyev's book "Layered Design for Ruby on
12
+ Rails Applications" (Packt).
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # rails-hyperdrive-layered-rails
2
+
3
+ A [rails-hyperdrive](https://github.com/rails-hyperdrive/rails-hyperdrive) companion gem
4
+ shipping Vladimir Dementyev's **Layered Rails** skill — the coding-agent
5
+ distillation of *Layered Design for Ruby on Rails Applications*.
6
+
7
+ The architecture core installs into any Rails app. Each per-gem reference manual
8
+ installs **only when that gem is in the app's bundle**.
9
+
10
+ ```ruby
11
+ group :development do
12
+ gem "rails-hyperdrive"
13
+ gem "rails-hyperdrive-layered-rails"
14
+ end
15
+ ```
16
+
17
+ ```sh
18
+ bin/rails hyperdrive:init
19
+ ```
20
+
21
+ ## What gets installed
22
+
23
+ One skill at `.claude/skills/layered-rails/`: a router `SKILL.md` plus workflows,
24
+ pattern/anti-pattern/topic references, and refactoring examples.
25
+
26
+ Nine reference manuals under `references/gems/` are gated on the app's bundle:
27
+
28
+ | Reference | Installs when the app bundles |
29
+ |---|---|
30
+ | `action-policy.md` | `action_policy` |
31
+ | `view-component.md` | `view_component` |
32
+ | `anyway-config.md` | `anyway_config` |
33
+ | `active-delivery.md` | `active_delivery` |
34
+ | `alba.md` | `alba` |
35
+ | `workflow.md` | `workflow` or `workflow-activerecord` |
36
+ | `rubanok.md` | `rubanok` |
37
+ | `active-agent.md` | `activeagent` |
38
+ | `active-job-performs.md` | `active_job-performs` |
39
+
40
+ A Rails app bundling none of them gets the architecture guidance with no dead
41
+ weight and no dangling links; an app bundling `alba` and `action_policy` gets
42
+ those two manuals and a Gem References table naming exactly those two.
43
+
44
+ Guidance that *recommends adopting* a gem stays unconditional — a reader should
45
+ still learn that Active Delivery exists. Only the reference manuals, which are
46
+ useless without the gem, are gated.
47
+
48
+ ## How the conditioning works
49
+
50
+ Two rails-hyperdrive mechanisms, both declared in `SKILL.md`'s frontmatter and
51
+ resolved at install time against the app's `Gemfile.lock`:
52
+
53
+ **Per-file gating** — a `conditional:` map keyed by supporting-file path:
54
+
55
+ ```yaml
56
+ conditional:
57
+ references/gems/alba.md:
58
+ gem: "alba"
59
+ references/gems/workflow.md:
60
+ gem: "workflow, workflow-activerecord"
61
+ ```
62
+
63
+ The listed file installs only when at least one named gem is bundled. The files
64
+ themselves are byte-identical to upstream.
65
+
66
+ **Templating** — the router ships as `SKILL.md.erb` and is rendered with
67
+ `gem?`, `any_gem?`, and `gem_version` helpers. Its Gem References table lists
68
+ only bundled gems, so gating a manual never leaves a link pointing at a file
69
+ that was not installed. When no target gem is bundled the section disappears
70
+ entirely. Rendered output installs as plain `SKILL.md`.
71
+
72
+ ## Upstream sync
73
+
74
+ Everything except `SKILL.md.erb` is vendored verbatim from
75
+ [palkan/skills](https://github.com/palkan/skills); `UPSTREAM` records the ref.
76
+
77
+ ```sh
78
+ bin/vendor-upstream [ref] # default: v2.0.1
79
+ ```
80
+
81
+ The script re-derives both adaptations from upstream's own content — the
82
+ `conditional:` map and the ERB table are generated from the Gem References table
83
+ it parses out of upstream's `SKILL.md`. Upstream edits therefore carry over
84
+ wholesale. A gem reference the script cannot map to target gems aborts the
85
+ vendor rather than passing through unconditioned.
86
+
87
+ ## License
88
+
89
+ MIT. Skill content © Vladimir Dementyev — see `LICENSE` and `UPSTREAM`.
data/UPSTREAM ADDED
@@ -0,0 +1,4 @@
1
+ repo: https://github.com/palkan/skills.git
2
+ ref: v2.0.1
3
+ sha: f4e8cd90ae388339d53bc05a3826034d0df56255
4
+ path: layered-rails/skills/layered-rails
@@ -0,0 +1,315 @@
1
+ ---
2
+ name: layered-rails
3
+ description: Write, refactor, and review Rails code using layered architecture principles from "Layered Design for Ruby on Rails Applications". Use when writing or refactoring Rails code — models, controllers, services, jobs, mailers, policies, forms, query objects, presenters, view components, state machines, serializers, or AI/LLM features — to apply correct patterns and avoid layer violations; and when reviewing Rails code, PRs, or diffs for layer violations, fat controllers/models, anemic models, callback misuse, god objects, or specification-test failures. Triggers on "layered design", "architecture layers", "abstraction layer", "specification test", "layer violation", "fat controller/model", "god object", "anemic model", "extract service/callback/policy/concern", "service object", "form object", "policy object", "query object", "value object", "presenter", "view component", "state machine", "Active Delivery", "callback scoring", "Rails refactor/review", "Rails patterns/best practices".
4
+ allowed-tools:
5
+ - Grep
6
+ - Glob
7
+ - Read
8
+ - Task
9
+ gem: railties
10
+ versions: ">= 7.0"
11
+ conditional:
12
+ references/gems/action-policy.md:
13
+ gem: "action_policy"
14
+ references/gems/view-component.md:
15
+ gem: "view_component"
16
+ references/gems/anyway-config.md:
17
+ gem: "anyway_config"
18
+ references/gems/active-delivery.md:
19
+ gem: "active_delivery"
20
+ references/gems/alba.md:
21
+ gem: "alba"
22
+ references/gems/workflow.md:
23
+ gem: "workflow, workflow-activerecord"
24
+ references/gems/rubanok.md:
25
+ gem: "rubanok"
26
+ references/gems/active-agent.md:
27
+ gem: "activeagent"
28
+ references/gems/active-job-performs.md:
29
+ gem: "active_job-performs"
30
+ ---
31
+
32
+ # Layered Rails
33
+
34
+ Design and review Rails applications using layered architecture principles.
35
+
36
+ ## Quick Start
37
+
38
+ Rails applications are organized into four architecture layers with **unidirectional data flow**:
39
+
40
+ ```
41
+ ┌─────────────────────────────────────────┐
42
+ │ PRESENTATION LAYER │
43
+ │ Controllers, Views, Channels, Mailers │
44
+ └─────────────────────────────────────────┘
45
+
46
+ ┌─────────────────────────────────────────┐
47
+ │ APPLICATION LAYER │
48
+ │ Service Objects, Form Objects, etc. │
49
+ └─────────────────────────────────────────┘
50
+
51
+ ┌─────────────────────────────────────────┐
52
+ │ DOMAIN LAYER │
53
+ │ Models, Value Objects, Domain Events │
54
+ └─────────────────────────────────────────┘
55
+
56
+ ┌─────────────────────────────────────────┐
57
+ │ INFRASTRUCTURE LAYER │
58
+ │ Active Record, APIs, File Storage │
59
+ └─────────────────────────────────────────┘
60
+ ```
61
+
62
+ **Core Rule:** Lower layers must never depend on higher layers.
63
+
64
+ See [Architecture Layers Reference](references/core/architecture-layers.md) for the full layer responsibilities and the Four Rules deep-dive.
65
+
66
+ ## What this skill is for
67
+
68
+ Use this skill when:
69
+
70
+ 1. **Analyzing a codebase** — apply the [architecture analysis](workflows/analyze.md) for a full audit, or zoom in with the [service-layer audit](workflows/analyze-services.md), [callback analysis](workflows/analyze-callbacks.md), or [god-object analysis](workflows/analyze-gods.md).
71
+ 2. **Reviewing code changes** — run the [code review workflow](workflows/review.md) on a diff, file, or branch.
72
+ 3. **Running the specification test** — use the [spec-test workflow](workflows/spec-test.md) on a single file or directory to evaluate whether code belongs in its current layer.
73
+ 4. **Planning gradual adoption** — generate a phased roadmap with the [layerification plan workflow](workflows/plan.md), focused on a goal like "introduce authorization" or "decompose god objects."
74
+ 5. **Planning a feature** — I'll apply the layered principles below to whichever code you're about to write.
75
+ 6. **Implementing a specific pattern** — authorization, notifications, view components, AI integration, etc. — see the Pattern Catalog and Topic References below.
76
+
77
+ In Claude Code with this skill installed as a plugin (`/plugin install layered-rails@palkan-skills`), each workflow above is also reachable as a slash command — see [Slash Commands](#slash-commands). Natural-language requests ("review this file with layered-rails", "run the specification test on `app/models/order.rb`") work in any environment that has the skill loaded.
78
+
79
+ ## Workflows
80
+
81
+ Reusable procedures bundled inside this skill. Read the file and apply it to the target code:
82
+
83
+ - [Architecture analysis](workflows/analyze.md) — full layered-architecture audit of a Rails codebase
84
+ - [Code review](workflows/review.md) — review a diff or file set for layer violations
85
+ - [Specification test](workflows/spec-test.md) — evaluate whether code belongs in its current layer
86
+ - [Service-layer audit](workflows/analyze-services.md) — deep audit of `app/services/` and service-like classes (per-cluster proposals, contracts, layer hygiene)
87
+ - [Callback analysis](workflows/analyze-callbacks.md) — score Active Record callbacks and find extraction candidates
88
+ - [God-object analysis](workflows/analyze-gods.md) — identify oversized models and recommend decomposition
89
+ - [Gradual layerification plan](workflows/plan.md) — incremental roadmap for adopting layered patterns
90
+
91
+ ## Core Principles
92
+
93
+ ### The Four Rules
94
+
95
+ 1. **Unidirectional Data Flow** - Data flows top-to-bottom only
96
+ 2. **No Reverse Dependencies** - Lower layers never depend on higher layers
97
+ 3. **Abstraction Boundaries** - Each abstraction belongs to exactly one layer
98
+ 4. **Minimize Connections** - Fewer inter-layer connections = looser coupling
99
+
100
+ ### Common Violations
101
+
102
+ | Violation | Example | Fix |
103
+ |-----------|---------|-----|
104
+ | Model uses Current | `Current.user` in model | Pass user as explicit parameter |
105
+ | Service accepts request | `param :request` in service | Extract value object from request |
106
+ | Controller has business logic | Pricing calculations in action | Extract to service or model |
107
+ | Anemic models | All logic in services | Keep domain logic in models |
108
+
109
+ | Category | Reference |
110
+ |----------|-----------|
111
+ | Layer violations (Current in models, request in services, notifications in models, business logic in controllers) | [layer-violations.md](references/anti-patterns/layer-violations.md) |
112
+ | Service objects (anemic models, bag of random objects, premature abstraction) | [service-objects.md](references/anti-patterns/service-objects.md) |
113
+ | Callbacks (operation callbacks, skip callbacks, control flags) | [callbacks.md](references/anti-patterns/callbacks.md) |
114
+ | Concerns (code-slicing, overgrown) | [concerns.md](references/anti-patterns/concerns.md) |
115
+ | Helpers (HTML construction in helpers) | [helpers.md](references/anti-patterns/helpers.md) |
116
+ | Jobs (anemic jobs) | [jobs.md](references/anti-patterns/jobs.md) |
117
+ | Testing (testing wrong layer) | [testing.md](references/anti-patterns/testing.md) |
118
+
119
+ ### The Specification Test
120
+
121
+ > If the specification of an object describes features beyond the primary responsibility of its abstraction layer, such features should be extracted into lower layers.
122
+
123
+ **How to apply:**
124
+ 1. List responsibilities the code handles
125
+ 2. Evaluate each against the layer's primary concern
126
+ 3. Extract misplaced responsibilities to appropriate layers
127
+
128
+ See [Specification Test Reference](references/core/specification-test.md) for detailed guide.
129
+
130
+ ## Pattern Catalog
131
+
132
+ | Pattern | Layer | Use When | Reference |
133
+ |---------|-------|----------|-----------|
134
+ | Service Object | Application | Orchestrating domain operations | [service-objects.md](references/patterns/service-objects.md) |
135
+ | Query Object | Domain | Complex, reusable queries | [query-objects.md](references/patterns/query-objects.md) |
136
+ | Form Object | Presentation | Multi-model forms, complex validation | [form-objects.md](references/patterns/form-objects.md) |
137
+ | Filter Object | Presentation | Request parameter transformation | [filter-objects.md](references/patterns/filter-objects.md) |
138
+ | Presenter | Presentation | View-specific logic, multiple models | [presenters.md](references/patterns/presenters.md) |
139
+ | Serializer | Presentation | API response formatting | [serializers.md](references/patterns/serializers.md) |
140
+ | Policy Object | Application | Authorization decisions | [policy-objects.md](references/patterns/policy-objects.md) |
141
+ | Value Object | Domain | Immutable, identity-less concepts | [value-objects.md](references/patterns/value-objects.md) |
142
+ | Collaborator Object | Domain | A slice of one model's behavior in a typed delegate | [collaborator-objects.md](references/patterns/collaborator-objects.md) |
143
+ | State Machine | Domain | States, events, transitions | [state-machines.md](references/patterns/state-machines.md) |
144
+ | Concern | Domain | Shared behavioral extraction | [concerns.md](references/patterns/concerns.md) |
145
+ | Repository | Application | **Last resort** — returning custom domain objects mapped from AR data, after AR scopes (simple) and query objects (query building) are insufficient | [repositories.md](references/patterns/repositories.md) |
146
+
147
+ ### Pattern Selection Guide
148
+
149
+ **"Where should this code go?"**
150
+
151
+ | If you have... | Consider... |
152
+ |----------------|-------------|
153
+ | Complex multi-model form | Form Object |
154
+ | Request parameter filtering/transformation | Filter Object |
155
+ | View-specific formatting | Presenter |
156
+ | Complex database query used in multiple places | Query Object |
157
+ | Business operation spanning multiple models | Service Object (as waiting room) |
158
+ | Authorization rules | Policy Object |
159
+ | Multi-channel notifications | Delivery Object (Active Delivery) |
160
+
161
+ **Remember:** Services are a "waiting room" for code until proper abstractions emerge. Don't let `app/services` become a bag of random objects.
162
+
163
+ ## Refactoring Scenarios
164
+
165
+ Canonical before/after transformations for the most common layerification moves. The [layerification plan workflow](workflows/plan.md) uses these as reference templates when proposing phases.
166
+
167
+ | Scenario | Goal area | Reference |
168
+ |----------|-----------|-----------|
169
+ | Extract callbacks to service | callbacks, after_create chains | [callbacks-to-service.md](examples/callbacks-to-service.md) |
170
+ | Extract authorization to policy | authorization, permissions | [authorization-to-policy.md](examples/authorization-to-policy.md) |
171
+ | Extract query logic to query object | complex scopes, reporting queries | [query-to-query-object.md](examples/query-to-query-object.md) |
172
+ | Extract Current from model | Current.* in domain | [current-from-model.md](examples/current-from-model.md) |
173
+ | Decompose god object with associated objects | god model, large User/Account | [god-object-decomposition.md](examples/god-object-decomposition.md) |
174
+ | Replace implicit state machine | timestamp-based status | [implicit-to-explicit-state-machine.md](examples/implicit-to-explicit-state-machine.md) |
175
+ | Extract view logic to presenter | template logic, formatting | [view-logic-to-presenter.md](examples/view-logic-to-presenter.md) |
176
+ | Form object for complex input | fat controllers, multi-model forms | [complex-input-to-form-object.md](examples/complex-input-to-form-object.md) |
177
+
178
+ ## Slash Commands
179
+
180
+ These slash commands are available **only when this skill is installed as a Claude Code plugin** (`/plugin install layered-rails@palkan-skills`). When the skill is installed via [skills.sh](https://skills.sh/) or any other path that delivers `skills/layered-rails/` without the surrounding plugin, the commands won't be present — invoke the corresponding workflow directly (see [Workflows](#workflows)) or just ask in plain language.
181
+
182
+ | Command | Workflow | Purpose |
183
+ |---------|----------|---------|
184
+ | `/layered-rails:review` | [review](workflows/review.md) | Review code changes from a layered architecture perspective |
185
+ | `/layered-rails:spec-test` | [spec-test](workflows/spec-test.md) | Run specification test on specific files |
186
+ | `/layered-rails:analyze` | [analyze](workflows/analyze.md) | Full codebase abstraction-layer analysis |
187
+ | `/layered-rails:analyze-services` | [analyze-services](workflows/analyze-services.md) | Audit `app/services/` and service-like classes — conventions, clusters, layer hygiene, test consequences |
188
+ | `/layered-rails:analyze-callbacks` | [analyze-callbacks](workflows/analyze-callbacks.md) | Score model callbacks, find extraction candidates |
189
+ | `/layered-rails:analyze-gods` | [analyze-gods](workflows/analyze-gods.md) | Find god objects via churn × complexity |
190
+ | `/layered-rails:plan [goal]` | [plan](workflows/plan.md) | Plan gradual adoption of layered patterns |
191
+
192
+ ## Topic References
193
+
194
+ For deep dives on specific topics:
195
+
196
+ | Topic | Reference |
197
+ |-------|-----------|
198
+ | Authorization (RBAC, ABAC, policies) | [authorization.md](references/topics/authorization.md) |
199
+ | Notifications (multi-channel delivery) | [notifications.md](references/topics/notifications.md) |
200
+ | View Components | [view-components.md](references/topics/view-components.md) |
201
+ | AI Integration (LLM, agents, RAG, MCP) | [ai-integration.md](references/topics/ai-integration.md) |
202
+ | Configuration | [configuration.md](references/topics/configuration.md) |
203
+ | Callbacks (scoring, extraction) | [callbacks.md](references/topics/callbacks.md) |
204
+ | Current Attributes | [current-attributes.md](references/topics/current-attributes.md) |
205
+ | Instrumentation (logging, metrics) | [instrumentation.md](references/topics/instrumentation.md) |
206
+
207
+ <%- gem_refs = [
208
+ ["action_policy", "Authorization framework", "action-policy.md", ["action_policy"]],
209
+ ["view_component", "Component framework", "view-component.md", ["view_component"]],
210
+ ["anyway_config", "Typed configuration", "anyway-config.md", ["anyway_config"]],
211
+ ["active_delivery", "Multi-channel notifications", "active-delivery.md", ["active_delivery"]],
212
+ ["alba", "JSON serialization", "alba.md", ["alba"]],
213
+ ["workflow", "State machines", "workflow.md", ["workflow", "workflow-activerecord"]],
214
+ ["rubanok", "Filter/transformation DSL", "rubanok.md", ["rubanok"]],
215
+ ["active_agent", "AI agent framework", "active-agent.md", ["activeagent"]],
216
+ ["active_job-performs", "Eliminate anemic jobs", "active-job-performs.md", ["active_job-performs"]],
217
+ ].select { |row| any_gem?(*row[3]) } -%>
218
+ <%- if gem_refs.any? -%>
219
+ ## Gem References
220
+
221
+ For library-specific guidance:
222
+
223
+ | Gem | Purpose | Reference |
224
+ |-----|---------|-----------|
225
+ <%- gem_refs.each do |label, purpose, file, _targets| -%>
226
+ | <%= label %> | <%= purpose %> | [<%= file %>](references/gems/<%= file %>) |
227
+ <%- end -%>
228
+
229
+ <%- end -%>
230
+ ## Extraction Signals
231
+
232
+ **When to extract from models:**
233
+
234
+ | Signal | Metric | Action |
235
+ |--------|--------|--------|
236
+ | God object | High churn × complexity | Decompose into concerns, delegates, or separate models |
237
+ | Operation callback | Score 1-2/5 | Extract to service or event handler |
238
+ | Code-slicing concern | Groups by artifact type | Convert to behavioral concern or extract |
239
+ | Current dependency | Model reads Current.* | Pass as explicit parameter |
240
+
241
+ **Callback Scoring:**
242
+ | Type | Score | Keep? |
243
+ |------|-------|-------|
244
+ | Transformer (compute values) | 5/5 | Yes |
245
+ | Normalizer (sanitize input) | 4/5 | Yes |
246
+ | Utility (counter caches) | 4/5 | Yes |
247
+ | Observer (side effects) | 2/5 | Maybe |
248
+ | Operation (business steps) | 1/5 | Extract |
249
+
250
+ See [Extraction Signals Reference](references/core/extraction-signals.md) for detailed guide.
251
+
252
+ ## Model Organization
253
+
254
+ Recommended order within model files:
255
+
256
+ ```ruby
257
+ class User < ApplicationRecord
258
+ # 1. Gems/DSL extensions
259
+ has_secure_password
260
+
261
+ # 2. Associations
262
+ belongs_to :account
263
+ has_many :posts
264
+
265
+ # 3. Enums
266
+ enum :status, { pending: 0, active: 1 }
267
+
268
+ # 4. Normalization
269
+ normalizes :email, with: -> { _1.strip.downcase }
270
+
271
+ # 5. Validations
272
+ validates :email, presence: true
273
+
274
+ # 6. Scopes
275
+ scope :active, -> { where(status: :active) }
276
+
277
+ # 7. Callbacks (transformers only)
278
+ before_validation :set_defaults
279
+
280
+ # 8. Delegations
281
+ delegate :name, to: :account, prefix: true
282
+
283
+ # 9. Public methods
284
+ def full_name = "#{first_name} #{last_name}"
285
+
286
+ # 10. Private methods
287
+ private
288
+
289
+ def set_defaults
290
+ self.locale ||= I18n.default_locale
291
+ end
292
+ end
293
+ ```
294
+
295
+ ## Success Checklist
296
+
297
+ Well-layered code:
298
+
299
+ - [ ] No reverse dependencies (lower layers don't depend on higher)
300
+ - [ ] Models don't access Current attributes
301
+ - [ ] Services don't accept request objects
302
+ - [ ] Controllers are thin (HTTP concerns only)
303
+ - [ ] Domain logic lives in models, not services
304
+ - [ ] Callbacks score 4+ or are extracted
305
+ - [ ] Concerns are behavioral, not code-slicing
306
+ - [ ] Abstractions don't span multiple layers
307
+ - [ ] Tests verify appropriate layer responsibilities
308
+
309
+ ## Guidelines
310
+
311
+ - **Use domain language** - Name models after business concepts (Participant, not User; Cloud, not GeneratedImage)
312
+ - **Patterns before abstractions** - Let code age before extracting; premature abstraction is worse than duplication
313
+ - **Services as waiting room** - Don't let `app/services` become permanent residence for code
314
+ - **Explicit over implicit** - Prefer explicit parameters over Current attributes
315
+ - **Extraction thresholds** - Consider extraction when methods exceed 15 lines or call external APIs
@@ -0,0 +1,79 @@
1
+ # Extract Authorization to Policy
2
+
3
+ Replace duplicated controller-side authorization checks with a policy object.
4
+
5
+ ## Before
6
+
7
+ ```ruby
8
+ class PostsController < ApplicationController
9
+ def update
10
+ @post = Post.find(params[:id])
11
+
12
+ # Authorization scattered in controller
13
+ unless current_user.admin? || @post.author == current_user
14
+ redirect_to posts_path, alert: "Not authorized"
15
+ return
16
+ end
17
+
18
+ @post.update!(post_params)
19
+ redirect_to @post
20
+ end
21
+
22
+ def destroy
23
+ @post = Post.find(params[:id])
24
+
25
+ # Duplicated logic
26
+ unless current_user.admin?
27
+ redirect_to posts_path, alert: "Not authorized"
28
+ return
29
+ end
30
+
31
+ @post.destroy!
32
+ redirect_to posts_path
33
+ end
34
+ end
35
+ ```
36
+
37
+ ## After
38
+
39
+ ```ruby
40
+ # app/policies/post_policy.rb
41
+ class PostPolicy < ApplicationPolicy
42
+ def update?
43
+ owner? || admin?
44
+ end
45
+
46
+ def destroy?
47
+ admin?
48
+ end
49
+
50
+ private
51
+
52
+ def owner?
53
+ record.author_id == user.id
54
+ end
55
+
56
+ def admin?
57
+ user.admin?
58
+ end
59
+ end
60
+
61
+ # app/controllers/posts_controller.rb
62
+ class PostsController < ApplicationController
63
+ def update
64
+ @post = Post.find(params[:id])
65
+ authorize! @post
66
+
67
+ @post.update!(post_params)
68
+ redirect_to @post
69
+ end
70
+
71
+ def destroy
72
+ @post = Post.find(params[:id])
73
+ authorize! @post
74
+
75
+ @post.destroy!
76
+ redirect_to posts_path
77
+ end
78
+ end
79
+ ```
@@ -0,0 +1,72 @@
1
+ # Extract Callbacks to Service
2
+
3
+ Move multi-step user-creation side effects out of `after_create` callbacks into an explicit service.
4
+
5
+ ## Before
6
+
7
+ ```ruby
8
+ class User < ApplicationRecord
9
+ after_create :send_welcome_email
10
+ after_create :create_default_workspace
11
+ after_create :notify_admin
12
+ after_create :track_signup
13
+
14
+ private
15
+
16
+ def send_welcome_email
17
+ UserMailer.welcome(self).deliver_later
18
+ end
19
+
20
+ def create_default_workspace
21
+ workspaces.create!(name: "My Workspace")
22
+ end
23
+
24
+ def notify_admin
25
+ AdminMailer.new_user(self).deliver_later
26
+ end
27
+
28
+ def track_signup
29
+ Analytics.track("user_signed_up", user_id: id)
30
+ end
31
+ end
32
+ ```
33
+
34
+ ## After
35
+
36
+ ```ruby
37
+ # app/models/user.rb
38
+ class User < ApplicationRecord
39
+ before_validation :normalize_email
40
+
41
+ private
42
+
43
+ def normalize_email
44
+ self.email = email&.downcase&.strip
45
+ end
46
+ end
47
+
48
+ # app/services/users/create.rb
49
+ class Users::Create < ApplicationService
50
+ def call(params)
51
+ user = User.create!(params)
52
+
53
+ UserMailer.welcome(user).deliver_later
54
+ user.workspaces.create!(name: "My Workspace")
55
+ AdminMailer.new_user(user).deliver_later
56
+ Analytics.track("user_signed_up", user_id: user.id)
57
+
58
+ user
59
+ end
60
+ end
61
+
62
+ # app/controllers/users_controller.rb
63
+ class UsersController < ApplicationController
64
+ def create
65
+ @user = Users::Create.call(user_params)
66
+ redirect_to @user, notice: "Welcome!"
67
+ rescue ActiveRecord::RecordInvalid => e
68
+ @user = e.record
69
+ render :new, status: :unprocessable_entity
70
+ end
71
+ end
72
+ ```