ag-cortex 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.
- package/.agent/commands/test-browser.md +339 -0
- package/.agent/rules/00-constitution.md +46 -0
- package/.agent/rules/project-rules.md +49 -0
- package/.agent/skills/agent-browser/SKILL.md +223 -0
- package/.agent/skills/agent-native-architecture/SKILL.md +435 -0
- package/.agent/skills/agent-native-architecture/references/action-parity-discipline.md +409 -0
- package/.agent/skills/agent-native-architecture/references/agent-execution-patterns.md +467 -0
- package/.agent/skills/agent-native-architecture/references/agent-native-testing.md +582 -0
- package/.agent/skills/agent-native-architecture/references/architecture-patterns.md +478 -0
- package/.agent/skills/agent-native-architecture/references/dynamic-context-injection.md +338 -0
- package/.agent/skills/agent-native-architecture/references/files-universal-interface.md +301 -0
- package/.agent/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +359 -0
- package/.agent/skills/agent-native-architecture/references/mcp-tool-design.md +506 -0
- package/.agent/skills/agent-native-architecture/references/mobile-patterns.md +871 -0
- package/.agent/skills/agent-native-architecture/references/product-implications.md +443 -0
- package/.agent/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +317 -0
- package/.agent/skills/agent-native-architecture/references/self-modification.md +269 -0
- package/.agent/skills/agent-native-architecture/references/shared-workspace-architecture.md +680 -0
- package/.agent/skills/agent-native-architecture/references/system-prompt-design.md +250 -0
- package/.agent/skills/agent-native-reviewer/SKILL.md +246 -0
- package/.agent/skills/andrew-kane-gem-writer/SKILL.md +184 -0
- package/.agent/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/.agent/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/.agent/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/.agent/skills/andrew-kane-gem-writer/references/resources.md +119 -0
- package/.agent/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/.agent/skills/ankane-readme-writer/SKILL.md +50 -0
- package/.agent/skills/architecture-strategist/SKILL.md +52 -0
- package/.agent/skills/best-practices-researcher/SKILL.md +100 -0
- package/.agent/skills/bug-reproduction-validator/SKILL.md +67 -0
- package/.agent/skills/code-simplicity-reviewer/SKILL.md +85 -0
- package/.agent/skills/coding-tutor/.claude-plugin/plugin.json +9 -0
- package/.agent/skills/coding-tutor/README.md +37 -0
- package/.agent/skills/coding-tutor/commands/quiz-me.md +1 -0
- package/.agent/skills/coding-tutor/commands/sync-tutorials.md +25 -0
- package/.agent/skills/coding-tutor/commands/teach-me.md +1 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/SKILL.md +214 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/create_tutorial.py +202 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/index_tutorials.py +203 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/quiz_priority.py +190 -0
- package/.agent/skills/coding-tutor/skills/coding-tutor/scripts/setup_tutorials.py +132 -0
- package/.agent/skills/compound-docs/SKILL.md +510 -0
- package/.agent/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/.agent/skills/compound-docs/assets/resolution-template.md +93 -0
- package/.agent/skills/compound-docs/references/yaml-schema.md +65 -0
- package/.agent/skills/compound-docs/schema.yaml +176 -0
- package/.agent/skills/create-agent-skills/SKILL.md +299 -0
- package/.agent/skills/create-agent-skills/references/api-security.md +226 -0
- package/.agent/skills/create-agent-skills/references/be-clear-and-direct.md +531 -0
- package/.agent/skills/create-agent-skills/references/best-practices.md +404 -0
- package/.agent/skills/create-agent-skills/references/common-patterns.md +595 -0
- package/.agent/skills/create-agent-skills/references/core-principles.md +437 -0
- package/.agent/skills/create-agent-skills/references/executable-code.md +175 -0
- package/.agent/skills/create-agent-skills/references/iteration-and-testing.md +474 -0
- package/.agent/skills/create-agent-skills/references/official-spec.md +185 -0
- package/.agent/skills/create-agent-skills/references/recommended-structure.md +168 -0
- package/.agent/skills/create-agent-skills/references/skill-structure.md +372 -0
- package/.agent/skills/create-agent-skills/references/using-scripts.md +113 -0
- package/.agent/skills/create-agent-skills/references/using-templates.md +112 -0
- package/.agent/skills/create-agent-skills/references/workflows-and-validation.md +510 -0
- package/.agent/skills/create-agent-skills/templates/router-skill.md +73 -0
- package/.agent/skills/create-agent-skills/templates/simple-skill.md +33 -0
- package/.agent/skills/create-agent-skills/workflows/add-reference.md +96 -0
- package/.agent/skills/create-agent-skills/workflows/add-script.md +93 -0
- package/.agent/skills/create-agent-skills/workflows/add-template.md +74 -0
- package/.agent/skills/create-agent-skills/workflows/add-workflow.md +120 -0
- package/.agent/skills/create-agent-skills/workflows/audit-skill.md +138 -0
- package/.agent/skills/create-agent-skills/workflows/create-domain-expertise-skill.md +605 -0
- package/.agent/skills/create-agent-skills/workflows/create-new-skill.md +191 -0
- package/.agent/skills/create-agent-skills/workflows/get-guidance.md +121 -0
- package/.agent/skills/create-agent-skills/workflows/upgrade-to-router.md +161 -0
- package/.agent/skills/create-agent-skills/workflows/verify-skill.md +204 -0
- package/.agent/skills/data-integrity-guardian/SKILL.md +70 -0
- package/.agent/skills/data-migration-expert/SKILL.md +97 -0
- package/.agent/skills/deployment-verification-agent/SKILL.md +159 -0
- package/.agent/skills/design-implementation-reviewer/SKILL.md +85 -0
- package/.agent/skills/design-iterator/SKILL.md +197 -0
- package/.agent/skills/dhh-rails-reviewer/SKILL.md +45 -0
- package/.agent/skills/dhh-rails-style/SKILL.md +184 -0
- package/.agent/skills/dhh-rails-style/references/architecture.md +653 -0
- package/.agent/skills/dhh-rails-style/references/controllers.md +303 -0
- package/.agent/skills/dhh-rails-style/references/frontend.md +510 -0
- package/.agent/skills/dhh-rails-style/references/gems.md +266 -0
- package/.agent/skills/dhh-rails-style/references/models.md +359 -0
- package/.agent/skills/dhh-rails-style/references/testing.md +338 -0
- package/.agent/skills/dspy-ruby/SKILL.md +594 -0
- package/.agent/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/.agent/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/.agent/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/.agent/skills/dspy-ruby/references/core-concepts.md +265 -0
- package/.agent/skills/dspy-ruby/references/optimization.md +623 -0
- package/.agent/skills/dspy-ruby/references/providers.md +305 -0
- package/.agent/skills/every-style-editor/SKILL.md +134 -0
- package/.agent/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/.agent/skills/figma-design-sync/SKILL.md +166 -0
- package/.agent/skills/file-todos/SKILL.md +251 -0
- package/.agent/skills/file-todos/assets/todo-template.md +155 -0
- package/.agent/skills/framework-docs-researcher/SKILL.md +83 -0
- package/.agent/skills/frontend-design/SKILL.md +42 -0
- package/.agent/skills/gemini-imagegen/SKILL.md +237 -0
- package/.agent/skills/gemini-imagegen/requirements.txt +2 -0
- package/.agent/skills/gemini-imagegen/scripts/compose_images.py +168 -0
- package/.agent/skills/gemini-imagegen/scripts/edit_image.py +157 -0
- package/.agent/skills/gemini-imagegen/scripts/gemini_images.py +265 -0
- package/.agent/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/.agent/skills/gemini-imagegen/scripts/multi_turn_chat.py +215 -0
- package/.agent/skills/git-history-analyzer/SKILL.md +42 -0
- package/.agent/skills/git-worktree/SKILL.md +302 -0
- package/.agent/skills/git-worktree/scripts/worktree-manager.sh +345 -0
- package/.agent/skills/julik-frontend-races-reviewer/SKILL.md +222 -0
- package/.agent/skills/kieran-python-reviewer/SKILL.md +104 -0
- package/.agent/skills/kieran-rails-reviewer/SKILL.md +86 -0
- package/.agent/skills/kieran-typescript-reviewer/SKILL.md +95 -0
- package/.agent/skills/lint/SKILL.md +16 -0
- package/.agent/skills/pattern-recognition-specialist/SKILL.md +57 -0
- package/.agent/skills/performance-oracle/SKILL.md +110 -0
- package/.agent/skills/pr-comment-resolver/SKILL.md +69 -0
- package/.agent/skills/rclone/SKILL.md +150 -0
- package/.agent/skills/rclone/scripts/check_setup.sh +60 -0
- package/.agent/skills/repo-research-analyst/SKILL.md +113 -0
- package/.agent/skills/security-sentinel/SKILL.md +93 -0
- package/.agent/skills/skill-creator/SKILL.md +209 -0
- package/.agent/skills/skill-creator/scripts/init_skill.py +304 -0
- package/.agent/skills/skill-creator/scripts/package_skill.py +112 -0
- package/.agent/skills/skill-creator/scripts/quick_validate.py +72 -0
- package/.agent/skills/spec-flow-analyzer/SKILL.md +113 -0
- package/.agent/skills/test-agent/SKILL.md +4 -0
- package/.agent/workflows/agent-native-audit.md +277 -0
- package/.agent/workflows/ask-user-question.md +21 -0
- package/.agent/workflows/changelog.md +137 -0
- package/.agent/workflows/compound.md +202 -0
- package/.agent/workflows/create-agent-skill.md +8 -0
- package/.agent/workflows/deepen-plan-research.md +334 -0
- package/.agent/workflows/deepen-plan-synthesis.md +182 -0
- package/.agent/workflows/deepen-plan.md +79 -0
- package/.agent/workflows/feature-video.md +342 -0
- package/.agent/workflows/generate-command.md +162 -0
- package/.agent/workflows/heal-skill.md +142 -0
- package/.agent/workflows/lfg.md +20 -0
- package/.agent/workflows/plan-analysis.md +67 -0
- package/.agent/workflows/plan-next-steps.md +63 -0
- package/.agent/workflows/plan-review.md +33 -0
- package/.agent/workflows/plan-synthesis.md +106 -0
- package/.agent/workflows/plan.md +49 -0
- package/.agent/workflows/report-bug.md +150 -0
- package/.agent/workflows/reproduce-bug.md +99 -0
- package/.agent/workflows/resolve-parallel.md +34 -0
- package/.agent/workflows/resolve-pr-parallel.md +49 -0
- package/.agent/workflows/resolve-todo-parallel.md +35 -0
- package/.agent/workflows/review-analysis.md +145 -0
- package/.agent/workflows/review-synthesis.md +262 -0
- package/.agent/workflows/review.md +64 -0
- package/.agent/workflows/ship.md +90 -0
- package/.agent/workflows/test-command.md +3 -0
- package/.agent/workflows/triage.md +310 -0
- package/.agent/workflows/work.md +157 -0
- package/.agent/workflows/xcode-test.md +332 -0
- package/LICENSE +22 -0
- package/README.md +49 -0
- package/bin/ag-cortex.js +54 -0
- package/lib/core.js +165 -0
- package/package.json +31 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Gems - DHH Rails Style
|
|
2
|
+
|
|
3
|
+
<what_they_use>
|
|
4
|
+
## What 37signals Uses
|
|
5
|
+
|
|
6
|
+
**Core Rails stack:**
|
|
7
|
+
- turbo-rails, stimulus-rails, importmap-rails
|
|
8
|
+
- propshaft (asset pipeline)
|
|
9
|
+
|
|
10
|
+
**Database-backed services (Solid suite):**
|
|
11
|
+
- solid_queue - background jobs
|
|
12
|
+
- solid_cache - caching
|
|
13
|
+
- solid_cable - WebSockets/Action Cable
|
|
14
|
+
|
|
15
|
+
**Authentication & Security:**
|
|
16
|
+
- bcrypt (for any password hashing needed)
|
|
17
|
+
|
|
18
|
+
**Their own gems:**
|
|
19
|
+
- geared_pagination (cursor-based pagination)
|
|
20
|
+
- lexxy (rich text editor)
|
|
21
|
+
- mittens (mailer utilities)
|
|
22
|
+
|
|
23
|
+
**Utilities:**
|
|
24
|
+
- rqrcode (QR code generation)
|
|
25
|
+
- redcarpet + rouge (Markdown rendering)
|
|
26
|
+
- web-push (push notifications)
|
|
27
|
+
|
|
28
|
+
**Deployment & Operations:**
|
|
29
|
+
- kamal (Docker deployment)
|
|
30
|
+
- thruster (HTTP/2 proxy)
|
|
31
|
+
- mission_control-jobs (job monitoring)
|
|
32
|
+
- autotuner (GC tuning)
|
|
33
|
+
</what_they_use>
|
|
34
|
+
|
|
35
|
+
<what_they_avoid>
|
|
36
|
+
## What They Deliberately Avoid
|
|
37
|
+
|
|
38
|
+
**Authentication:**
|
|
39
|
+
```
|
|
40
|
+
devise → Custom ~150-line auth
|
|
41
|
+
```
|
|
42
|
+
Why: Full control, no password liability with magic links, simpler.
|
|
43
|
+
|
|
44
|
+
**Authorization:**
|
|
45
|
+
```
|
|
46
|
+
pundit/cancancan → Simple role checks in models
|
|
47
|
+
```
|
|
48
|
+
Why: Most apps don't need policy objects. A method on the model suffices:
|
|
49
|
+
```ruby
|
|
50
|
+
class Board < ApplicationRecord
|
|
51
|
+
def editable_by?(user)
|
|
52
|
+
user.admin? || user == creator
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Background Jobs:**
|
|
58
|
+
```
|
|
59
|
+
sidekiq → Solid Queue
|
|
60
|
+
```
|
|
61
|
+
Why: Database-backed means no Redis, same transactional guarantees.
|
|
62
|
+
|
|
63
|
+
**Caching:**
|
|
64
|
+
```
|
|
65
|
+
redis → Solid Cache
|
|
66
|
+
```
|
|
67
|
+
Why: Database is already there, simpler infrastructure.
|
|
68
|
+
|
|
69
|
+
**Search:**
|
|
70
|
+
```
|
|
71
|
+
elasticsearch → Custom sharded search
|
|
72
|
+
```
|
|
73
|
+
Why: Built exactly what they need, no external service dependency.
|
|
74
|
+
|
|
75
|
+
**View Layer:**
|
|
76
|
+
```
|
|
77
|
+
view_component → Standard partials
|
|
78
|
+
```
|
|
79
|
+
Why: Partials work fine. ViewComponents add complexity without clear benefit for their use case.
|
|
80
|
+
|
|
81
|
+
**API:**
|
|
82
|
+
```
|
|
83
|
+
GraphQL → REST with Turbo
|
|
84
|
+
```
|
|
85
|
+
Why: REST is sufficient when you control both ends. GraphQL complexity not justified.
|
|
86
|
+
|
|
87
|
+
**Factories:**
|
|
88
|
+
```
|
|
89
|
+
factory_bot → Fixtures
|
|
90
|
+
```
|
|
91
|
+
Why: Fixtures are simpler, faster, and encourage thinking about data relationships upfront.
|
|
92
|
+
|
|
93
|
+
**Service Objects:**
|
|
94
|
+
```
|
|
95
|
+
Interactor, Trailblazer → Fat models
|
|
96
|
+
```
|
|
97
|
+
Why: Business logic stays in models. Methods like `card.close` instead of `CardCloser.call(card)`.
|
|
98
|
+
|
|
99
|
+
**Form Objects:**
|
|
100
|
+
```
|
|
101
|
+
Reform, dry-validation → params.expect + model validations
|
|
102
|
+
```
|
|
103
|
+
Why: Rails 7.1's `params.expect` is clean enough. Contextual validations on model.
|
|
104
|
+
|
|
105
|
+
**Decorators:**
|
|
106
|
+
```
|
|
107
|
+
Draper → View helpers + partials
|
|
108
|
+
```
|
|
109
|
+
Why: Helpers and partials are simpler. No decorator indirection.
|
|
110
|
+
|
|
111
|
+
**CSS:**
|
|
112
|
+
```
|
|
113
|
+
Tailwind, Sass → Native CSS
|
|
114
|
+
```
|
|
115
|
+
Why: Modern CSS has nesting, variables, layers. No build step needed.
|
|
116
|
+
|
|
117
|
+
**Frontend:**
|
|
118
|
+
```
|
|
119
|
+
React, Vue, SPAs → Turbo + Stimulus
|
|
120
|
+
```
|
|
121
|
+
Why: Server-rendered HTML with sprinkles of JS. SPA complexity not justified.
|
|
122
|
+
|
|
123
|
+
**Testing:**
|
|
124
|
+
```
|
|
125
|
+
RSpec → Minitest
|
|
126
|
+
```
|
|
127
|
+
Why: Simpler, faster boot, less DSL magic, ships with Rails.
|
|
128
|
+
</what_they_avoid>
|
|
129
|
+
|
|
130
|
+
<testing_philosophy>
|
|
131
|
+
## Testing Philosophy
|
|
132
|
+
|
|
133
|
+
**Minitest** - simpler, faster:
|
|
134
|
+
```ruby
|
|
135
|
+
class CardTest < ActiveSupport::TestCase
|
|
136
|
+
test "closing creates closure" do
|
|
137
|
+
card = cards(:one)
|
|
138
|
+
assert_difference -> { Card::Closure.count } do
|
|
139
|
+
card.close
|
|
140
|
+
end
|
|
141
|
+
assert card.closed?
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Fixtures** - loaded once, deterministic:
|
|
147
|
+
```yaml
|
|
148
|
+
# test/fixtures/cards.yml
|
|
149
|
+
open_card:
|
|
150
|
+
title: Open Card
|
|
151
|
+
board: main
|
|
152
|
+
creator: alice
|
|
153
|
+
|
|
154
|
+
closed_card:
|
|
155
|
+
title: Closed Card
|
|
156
|
+
board: main
|
|
157
|
+
creator: bob
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Dynamic timestamps** with ERB:
|
|
161
|
+
```yaml
|
|
162
|
+
recent:
|
|
163
|
+
title: Recent
|
|
164
|
+
created_at: <%= 1.hour.ago %>
|
|
165
|
+
|
|
166
|
+
old:
|
|
167
|
+
title: Old
|
|
168
|
+
created_at: <%= 1.month.ago %>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Time travel** for time-dependent tests:
|
|
172
|
+
```ruby
|
|
173
|
+
test "expires after 15 minutes" do
|
|
174
|
+
magic_link = MagicLink.create!(user: users(:alice))
|
|
175
|
+
|
|
176
|
+
travel 16.minutes
|
|
177
|
+
|
|
178
|
+
assert magic_link.expired?
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**VCR** for external APIs:
|
|
183
|
+
```ruby
|
|
184
|
+
VCR.use_cassette("stripe/charge") do
|
|
185
|
+
charge = Stripe::Charge.create(amount: 1000)
|
|
186
|
+
assert charge.paid
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Tests ship with features** - same commit, not before or after.
|
|
191
|
+
</testing_philosophy>
|
|
192
|
+
|
|
193
|
+
<decision_framework>
|
|
194
|
+
## Decision Framework
|
|
195
|
+
|
|
196
|
+
Before adding a gem, ask:
|
|
197
|
+
|
|
198
|
+
1. **Can vanilla Rails do this?**
|
|
199
|
+
- ActiveRecord can do most things Sequel can
|
|
200
|
+
- ActionMailer handles email fine
|
|
201
|
+
- ActiveJob works for most job needs
|
|
202
|
+
|
|
203
|
+
2. **Is the complexity worth it?**
|
|
204
|
+
- 150 lines of custom code vs. 10,000-line gem
|
|
205
|
+
- You'll understand your code better
|
|
206
|
+
- Fewer upgrade headaches
|
|
207
|
+
|
|
208
|
+
3. **Does it add infrastructure?**
|
|
209
|
+
- Redis? Consider database-backed alternatives
|
|
210
|
+
- External service? Consider building in-house
|
|
211
|
+
- Simpler infrastructure = fewer failure modes
|
|
212
|
+
|
|
213
|
+
4. **Is it from someone you trust?**
|
|
214
|
+
- 37signals gems: battle-tested at scale
|
|
215
|
+
- Well-maintained, focused gems: usually fine
|
|
216
|
+
- Kitchen-sink gems: probably overkill
|
|
217
|
+
|
|
218
|
+
**The philosophy:**
|
|
219
|
+
> "Build solutions before reaching for gems."
|
|
220
|
+
|
|
221
|
+
Not anti-gem, but pro-understanding. Use gems when they genuinely solve a problem you have, not a problem you might have.
|
|
222
|
+
</decision_framework>
|
|
223
|
+
|
|
224
|
+
<gem_patterns>
|
|
225
|
+
## Gem Usage Patterns
|
|
226
|
+
|
|
227
|
+
**Pagination:**
|
|
228
|
+
```ruby
|
|
229
|
+
# geared_pagination - cursor-based
|
|
230
|
+
class CardsController < ApplicationController
|
|
231
|
+
def index
|
|
232
|
+
@cards = @board.cards.geared(page: params[:page])
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Markdown:**
|
|
238
|
+
```ruby
|
|
239
|
+
# redcarpet + rouge
|
|
240
|
+
class MarkdownRenderer
|
|
241
|
+
def self.render(text)
|
|
242
|
+
Redcarpet::Markdown.new(
|
|
243
|
+
Redcarpet::Render::HTML.new(filter_html: true),
|
|
244
|
+
autolink: true,
|
|
245
|
+
fenced_code_blocks: true
|
|
246
|
+
).render(text)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Background jobs:**
|
|
252
|
+
```ruby
|
|
253
|
+
# solid_queue - no Redis
|
|
254
|
+
class ApplicationJob < ActiveJob::Base
|
|
255
|
+
queue_as :default
|
|
256
|
+
# Just works, backed by database
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Caching:**
|
|
261
|
+
```ruby
|
|
262
|
+
# solid_cache - no Redis
|
|
263
|
+
# config/environments/production.rb
|
|
264
|
+
config.cache_store = :solid_cache_store
|
|
265
|
+
```
|
|
266
|
+
</gem_patterns>
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Models - DHH Rails Style
|
|
2
|
+
|
|
3
|
+
<model_concerns>
|
|
4
|
+
## Concerns for Horizontal Behavior
|
|
5
|
+
|
|
6
|
+
Models heavily use concerns. A typical Card model includes 14+ concerns:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
class Card < ApplicationRecord
|
|
10
|
+
include Assignable
|
|
11
|
+
include Attachments
|
|
12
|
+
include Broadcastable
|
|
13
|
+
include Closeable
|
|
14
|
+
include Colored
|
|
15
|
+
include Eventable
|
|
16
|
+
include Golden
|
|
17
|
+
include Mentions
|
|
18
|
+
include Multistep
|
|
19
|
+
include Pinnable
|
|
20
|
+
include Postponable
|
|
21
|
+
include Readable
|
|
22
|
+
include Searchable
|
|
23
|
+
include Taggable
|
|
24
|
+
include Watchable
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Each concern is self-contained with associations, scopes, and methods.
|
|
29
|
+
|
|
30
|
+
**Naming:** Adjectives describing capability (`Closeable`, `Publishable`, `Watchable`)
|
|
31
|
+
</model_concerns>
|
|
32
|
+
|
|
33
|
+
<state_records>
|
|
34
|
+
## State as Records, Not Booleans
|
|
35
|
+
|
|
36
|
+
Instead of boolean columns, create separate records:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Instead of:
|
|
40
|
+
closed: boolean
|
|
41
|
+
is_golden: boolean
|
|
42
|
+
postponed: boolean
|
|
43
|
+
|
|
44
|
+
# Create records:
|
|
45
|
+
class Card::Closure < ApplicationRecord
|
|
46
|
+
belongs_to :card
|
|
47
|
+
belongs_to :creator, class_name: "User"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Card::Goldness < ApplicationRecord
|
|
51
|
+
belongs_to :card
|
|
52
|
+
belongs_to :creator, class_name: "User"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Card::NotNow < ApplicationRecord
|
|
56
|
+
belongs_to :card
|
|
57
|
+
belongs_to :creator, class_name: "User"
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Benefits:**
|
|
62
|
+
- Automatic timestamps (when it happened)
|
|
63
|
+
- Track who made changes
|
|
64
|
+
- Easy filtering via joins and `where.missing`
|
|
65
|
+
- Enables rich UI showing when/who
|
|
66
|
+
|
|
67
|
+
**In the model:**
|
|
68
|
+
```ruby
|
|
69
|
+
module Closeable
|
|
70
|
+
extend ActiveSupport::Concern
|
|
71
|
+
|
|
72
|
+
included do
|
|
73
|
+
has_one :closure, dependent: :destroy
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def closed?
|
|
77
|
+
closure.present?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def close(creator: Current.user)
|
|
81
|
+
create_closure!(creator: creator)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def reopen
|
|
85
|
+
closure&.destroy
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Querying:**
|
|
91
|
+
```ruby
|
|
92
|
+
Card.joins(:closure) # closed cards
|
|
93
|
+
Card.where.missing(:closure) # open cards
|
|
94
|
+
```
|
|
95
|
+
</state_records>
|
|
96
|
+
|
|
97
|
+
<callbacks>
|
|
98
|
+
## Callbacks - Used Sparingly
|
|
99
|
+
|
|
100
|
+
Only 38 callback occurrences across 30 files in Fizzy. Guidelines:
|
|
101
|
+
|
|
102
|
+
**Use for:**
|
|
103
|
+
- `after_commit` for async work
|
|
104
|
+
- `before_save` for derived data
|
|
105
|
+
- `after_create_commit` for side effects
|
|
106
|
+
|
|
107
|
+
**Avoid:**
|
|
108
|
+
- Complex callback chains
|
|
109
|
+
- Business logic in callbacks
|
|
110
|
+
- Synchronous external calls
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
class Card < ApplicationRecord
|
|
114
|
+
after_create_commit :notify_watchers_later
|
|
115
|
+
before_save :update_search_index, if: :title_changed?
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
def notify_watchers_later
|
|
119
|
+
NotifyWatchersJob.perform_later(self)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
</callbacks>
|
|
124
|
+
|
|
125
|
+
<scopes>
|
|
126
|
+
## Scope Naming
|
|
127
|
+
|
|
128
|
+
Standard scope names:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
class Card < ApplicationRecord
|
|
132
|
+
scope :chronologically, -> { order(created_at: :asc) }
|
|
133
|
+
scope :reverse_chronologically, -> { order(created_at: :desc) }
|
|
134
|
+
scope :alphabetically, -> { order(title: :asc) }
|
|
135
|
+
scope :latest, -> { reverse_chronologically.limit(10) }
|
|
136
|
+
|
|
137
|
+
# Standard eager loading
|
|
138
|
+
scope :preloaded, -> { includes(:creator, :assignees, :tags) }
|
|
139
|
+
|
|
140
|
+
# Parameterized
|
|
141
|
+
scope :indexed_by, ->(column) { order(column => :asc) }
|
|
142
|
+
scope :sorted_by, ->(column, direction = :asc) { order(column => direction) }
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
</scopes>
|
|
146
|
+
|
|
147
|
+
<poros>
|
|
148
|
+
## Plain Old Ruby Objects
|
|
149
|
+
|
|
150
|
+
POROs namespaced under parent models:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# app/models/event/description.rb
|
|
154
|
+
class Event::Description
|
|
155
|
+
def initialize(event)
|
|
156
|
+
@event = event
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def to_s
|
|
160
|
+
# Presentation logic for event description
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# app/models/card/eventable/system_commenter.rb
|
|
165
|
+
class Card::Eventable::SystemCommenter
|
|
166
|
+
def initialize(card)
|
|
167
|
+
@card = card
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def comment(message)
|
|
171
|
+
# Business logic
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# app/models/user/filtering.rb
|
|
176
|
+
class User::Filtering
|
|
177
|
+
# View context bundling
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**NOT used for service objects.** Business logic stays in models.
|
|
182
|
+
</poros>
|
|
183
|
+
|
|
184
|
+
<verbs_predicates>
|
|
185
|
+
## Method Naming
|
|
186
|
+
|
|
187
|
+
**Verbs** - Actions that change state:
|
|
188
|
+
```ruby
|
|
189
|
+
card.close
|
|
190
|
+
card.reopen
|
|
191
|
+
card.gild # make golden
|
|
192
|
+
card.ungild
|
|
193
|
+
board.publish
|
|
194
|
+
board.archive
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Predicates** - Queries derived from state:
|
|
198
|
+
```ruby
|
|
199
|
+
card.closed? # closure.present?
|
|
200
|
+
card.golden? # goldness.present?
|
|
201
|
+
board.published?
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Avoid** generic setters:
|
|
205
|
+
```ruby
|
|
206
|
+
# Bad
|
|
207
|
+
card.set_closed(true)
|
|
208
|
+
card.update_golden_status(false)
|
|
209
|
+
|
|
210
|
+
# Good
|
|
211
|
+
card.close
|
|
212
|
+
card.ungild
|
|
213
|
+
```
|
|
214
|
+
</verbs_predicates>
|
|
215
|
+
|
|
216
|
+
<validation_philosophy>
|
|
217
|
+
## Validation Philosophy
|
|
218
|
+
|
|
219
|
+
Minimal validations on models. Use contextual validations on form/operation objects:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# Model - minimal
|
|
223
|
+
class User < ApplicationRecord
|
|
224
|
+
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Form object - contextual
|
|
228
|
+
class Signup
|
|
229
|
+
include ActiveModel::Model
|
|
230
|
+
|
|
231
|
+
attr_accessor :email, :name, :terms_accepted
|
|
232
|
+
|
|
233
|
+
validates :email, :name, presence: true
|
|
234
|
+
validates :terms_accepted, acceptance: true
|
|
235
|
+
|
|
236
|
+
def save
|
|
237
|
+
return false unless valid?
|
|
238
|
+
User.create!(email: email, name: name)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Prefer database constraints** over model validations for data integrity:
|
|
244
|
+
```ruby
|
|
245
|
+
# migration
|
|
246
|
+
add_index :users, :email, unique: true
|
|
247
|
+
add_foreign_key :cards, :boards
|
|
248
|
+
```
|
|
249
|
+
</validation_philosophy>
|
|
250
|
+
|
|
251
|
+
<error_handling>
|
|
252
|
+
## Let It Crash Philosophy
|
|
253
|
+
|
|
254
|
+
Use bang methods that raise exceptions on failure:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Preferred - raises on failure
|
|
258
|
+
@card = Card.create!(card_params)
|
|
259
|
+
@card.update!(title: new_title)
|
|
260
|
+
@comment.destroy!
|
|
261
|
+
|
|
262
|
+
# Avoid - silent failures
|
|
263
|
+
@card = Card.create(card_params) # returns false on failure
|
|
264
|
+
if @card.save
|
|
265
|
+
# ...
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Let errors propagate naturally. Rails handles ActiveRecord::RecordInvalid with 422 responses.
|
|
270
|
+
</error_handling>
|
|
271
|
+
|
|
272
|
+
<default_values>
|
|
273
|
+
## Default Values with Lambdas
|
|
274
|
+
|
|
275
|
+
Use lambda defaults for associations with Current:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
class Card < ApplicationRecord
|
|
279
|
+
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
|
280
|
+
belongs_to :account, default: -> { Current.account }
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class Comment < ApplicationRecord
|
|
284
|
+
belongs_to :commenter, class_name: "User", default: -> { Current.user }
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Lambdas ensure dynamic resolution at creation time.
|
|
289
|
+
</default_values>
|
|
290
|
+
|
|
291
|
+
<rails_71_patterns>
|
|
292
|
+
## Rails 7.1+ Model Patterns
|
|
293
|
+
|
|
294
|
+
**Normalizes** - clean data before validation:
|
|
295
|
+
```ruby
|
|
296
|
+
class User < ApplicationRecord
|
|
297
|
+
normalizes :email, with: ->(email) { email.strip.downcase }
|
|
298
|
+
normalizes :phone, with: ->(phone) { phone.gsub(/\D/, "") }
|
|
299
|
+
end
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Delegated Types** - replace polymorphic associations:
|
|
303
|
+
```ruby
|
|
304
|
+
class Message < ApplicationRecord
|
|
305
|
+
delegated_type :messageable, types: %w[Comment Reply Announcement]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Now you get:
|
|
309
|
+
message.comment? # true if Comment
|
|
310
|
+
message.comment # returns the Comment
|
|
311
|
+
Message.comments # scope for Comment messages
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Store Accessor** - structured JSON storage:
|
|
315
|
+
```ruby
|
|
316
|
+
class User < ApplicationRecord
|
|
317
|
+
store :settings, accessors: [:theme, :notifications_enabled], coder: JSON
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
user.theme = "dark"
|
|
321
|
+
user.notifications_enabled = true
|
|
322
|
+
```
|
|
323
|
+
</rails_71_patterns>
|
|
324
|
+
|
|
325
|
+
<concern_guidelines>
|
|
326
|
+
## Concern Guidelines
|
|
327
|
+
|
|
328
|
+
- **50-150 lines** per concern (most are ~100)
|
|
329
|
+
- **Cohesive** - related functionality only
|
|
330
|
+
- **Named for capabilities** - `Closeable`, `Watchable`, not `CardHelpers`
|
|
331
|
+
- **Self-contained** - associations, scopes, methods together
|
|
332
|
+
- **Not for mere organization** - create when genuine reuse needed
|
|
333
|
+
|
|
334
|
+
**Touch chains** for cache invalidation:
|
|
335
|
+
```ruby
|
|
336
|
+
class Comment < ApplicationRecord
|
|
337
|
+
belongs_to :card, touch: true
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
class Card < ApplicationRecord
|
|
341
|
+
belongs_to :board, touch: true
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
When comment updates, card's `updated_at` changes, which cascades to board.
|
|
346
|
+
|
|
347
|
+
**Transaction wrapping** for related updates:
|
|
348
|
+
```ruby
|
|
349
|
+
class Card < ApplicationRecord
|
|
350
|
+
def close(creator: Current.user)
|
|
351
|
+
transaction do
|
|
352
|
+
create_closure!(creator: creator)
|
|
353
|
+
record_event(:closed)
|
|
354
|
+
notify_watchers_later
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
```
|
|
359
|
+
</concern_guidelines>
|