agent-notes 2.0.4__py3-none-any.whl
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.
- agent_notes/VERSION +1 -0
- agent_notes/__init__.py +1 -0
- agent_notes/__main__.py +4 -0
- agent_notes/cli.py +348 -0
- agent_notes/commands/__init__.py +27 -0
- agent_notes/commands/_install_helpers.py +262 -0
- agent_notes/commands/build.py +170 -0
- agent_notes/commands/doctor.py +112 -0
- agent_notes/commands/info.py +95 -0
- agent_notes/commands/install.py +99 -0
- agent_notes/commands/list.py +169 -0
- agent_notes/commands/memory.py +430 -0
- agent_notes/commands/regenerate.py +152 -0
- agent_notes/commands/set_role.py +143 -0
- agent_notes/commands/uninstall.py +26 -0
- agent_notes/commands/update.py +169 -0
- agent_notes/commands/validate.py +199 -0
- agent_notes/commands/wizard.py +720 -0
- agent_notes/config.py +154 -0
- agent_notes/data/agents/agents.yaml +352 -0
- agent_notes/data/agents/analyst.md +45 -0
- agent_notes/data/agents/api-reviewer.md +47 -0
- agent_notes/data/agents/architect.md +46 -0
- agent_notes/data/agents/coder.md +28 -0
- agent_notes/data/agents/database-specialist.md +45 -0
- agent_notes/data/agents/debugger.md +47 -0
- agent_notes/data/agents/devil.md +47 -0
- agent_notes/data/agents/devops.md +38 -0
- agent_notes/data/agents/explorer.md +23 -0
- agent_notes/data/agents/integrations.md +44 -0
- agent_notes/data/agents/lead.md +216 -0
- agent_notes/data/agents/performance-profiler.md +44 -0
- agent_notes/data/agents/refactorer.md +48 -0
- agent_notes/data/agents/reviewer.md +44 -0
- agent_notes/data/agents/security-auditor.md +44 -0
- agent_notes/data/agents/system-auditor.md +38 -0
- agent_notes/data/agents/tech-writer.md +32 -0
- agent_notes/data/agents/test-runner.md +36 -0
- agent_notes/data/agents/test-writer.md +39 -0
- agent_notes/data/cli/claude.yaml +25 -0
- agent_notes/data/cli/copilot.yaml +18 -0
- agent_notes/data/cli/opencode.yaml +22 -0
- agent_notes/data/commands/brainstorm.md +8 -0
- agent_notes/data/commands/debug.md +9 -0
- agent_notes/data/commands/review.md +10 -0
- agent_notes/data/global-claude.md +290 -0
- agent_notes/data/global-copilot.md +27 -0
- agent_notes/data/global-opencode.md +40 -0
- agent_notes/data/hooks/session-context.md.tpl +19 -0
- agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
- agent_notes/data/models/claude-opus-4-1.yaml +16 -0
- agent_notes/data/models/claude-opus-4-5.yaml +16 -0
- agent_notes/data/models/claude-opus-4-6.yaml +16 -0
- agent_notes/data/models/claude-opus-4-7.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
- agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4.yaml +16 -0
- agent_notes/data/pricing.yaml +33 -0
- agent_notes/data/roles/orchestrator.yaml +5 -0
- agent_notes/data/roles/reasoner.yaml +5 -0
- agent_notes/data/roles/scout.yaml +5 -0
- agent_notes/data/roles/worker.yaml +5 -0
- agent_notes/data/rules/code-quality.md +9 -0
- agent_notes/data/rules/safety.md +10 -0
- agent_notes/data/scripts/cost-report +211 -0
- agent_notes/data/skills/brainstorming/SKILL.md +57 -0
- agent_notes/data/skills/code-review/SKILL.md +64 -0
- agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
- agent_notes/data/skills/docker-compose/SKILL.md +318 -0
- agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
- agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
- agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
- agent_notes/data/skills/git/SKILL.md +87 -0
- agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
- agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
- agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
- agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
- agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
- agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
- agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
- agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
- agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
- agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
- agent_notes/data/skills/rails-lib/SKILL.md +101 -0
- agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
- agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
- agent_notes/data/skills/rails-models/SKILL.md +459 -0
- agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
- agent_notes/data/skills/rails-routes/SKILL.md +804 -0
- agent_notes/data/skills/rails-style/SKILL.md +538 -0
- agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
- agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
- agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
- agent_notes/data/skills/rails-validations/SKILL.md +108 -0
- agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
- agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
- agent_notes/data/skills/rails-views/SKILL.md +413 -0
- agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
- agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
- agent_notes/data/skills/tdd/SKILL.md +57 -0
- agent_notes/data/templates/__init__.py +1 -0
- agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__init__.py +1 -0
- agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/claude.py +44 -0
- agent_notes/data/templates/frontmatter/opencode.py +104 -0
- agent_notes/doctor_checks.py +189 -0
- agent_notes/domain/__init__.py +17 -0
- agent_notes/domain/agent.py +34 -0
- agent_notes/domain/cli_backend.py +40 -0
- agent_notes/domain/diagnostics.py +29 -0
- agent_notes/domain/diff.py +44 -0
- agent_notes/domain/model.py +27 -0
- agent_notes/domain/role.py +13 -0
- agent_notes/domain/rule.py +13 -0
- agent_notes/domain/skill.py +15 -0
- agent_notes/domain/state.py +46 -0
- agent_notes/install_state.py +11 -0
- agent_notes/registries/__init__.py +16 -0
- agent_notes/registries/_base.py +46 -0
- agent_notes/registries/agent_registry.py +107 -0
- agent_notes/registries/cli_registry.py +89 -0
- agent_notes/registries/model_registry.py +85 -0
- agent_notes/registries/role_registry.py +64 -0
- agent_notes/registries/rule_registry.py +80 -0
- agent_notes/registries/skill_registry.py +141 -0
- agent_notes/services/__init__.py +8 -0
- agent_notes/services/diagnostics/__init__.py +47 -0
- agent_notes/services/diagnostics/_checks.py +272 -0
- agent_notes/services/diagnostics/_display.py +346 -0
- agent_notes/services/diagnostics/_fix.py +169 -0
- agent_notes/services/diff.py +349 -0
- agent_notes/services/fs.py +195 -0
- agent_notes/services/install_state_builder.py +210 -0
- agent_notes/services/installer.py +293 -0
- agent_notes/services/memory_backend.py +155 -0
- agent_notes/services/rendering.py +329 -0
- agent_notes/services/session_context.py +23 -0
- agent_notes/services/settings_writer.py +79 -0
- agent_notes/services/state_store.py +249 -0
- agent_notes/services/ui.py +419 -0
- agent_notes/services/user_config.py +62 -0
- agent_notes/services/validation.py +67 -0
- agent_notes/state.py +21 -0
- agent_notes-2.0.4.dist-info/METADATA +14 -0
- agent_notes-2.0.4.dist-info/RECORD +162 -0
- agent_notes-2.0.4.dist-info/WHEEL +5 -0
- agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
- agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
- agent_notes-2.0.4.dist-info/top_level.txt +2 -0
- tests/conftest.py +20 -0
- tests/functional/__init__.py +0 -0
- tests/functional/test_build_commands.py +88 -0
- tests/functional/test_registries.py +128 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_build_output.py +129 -0
- tests/plugins/__init__.py +0 -0
- tests/plugins/test_agents.py +93 -0
- tests/plugins/test_skills.py +77 -0
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-routes
|
|
3
|
+
description: "Rails routing: RESTful resources, actions as resources, nested routes, and constraints"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Routes
|
|
8
|
+
|
|
9
|
+
Comprehensive patterns and best practices for Rails routing.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Core Philosophy
|
|
14
|
+
|
|
15
|
+
1. **RESTful resources** - Everything is a resource
|
|
16
|
+
2. **Actions as resources** - Model toggles/actions as singleton resources
|
|
17
|
+
3. **Zero custom actions** - No `post :custom_action`
|
|
18
|
+
4. **Nested resources** - Show relationships in URLs
|
|
19
|
+
5. **Clean URLs** - Use `scope module:` to organize without URL clutter
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Basic Resource Routing
|
|
24
|
+
|
|
25
|
+
### Standard CRUD Resources
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
resources :boards
|
|
29
|
+
# Generates:
|
|
30
|
+
# GET /boards → boards#index
|
|
31
|
+
# GET /boards/new → boards#new
|
|
32
|
+
# POST /boards → boards#create
|
|
33
|
+
# GET /boards/:id → boards#show
|
|
34
|
+
# GET /boards/:id/edit → boards#edit
|
|
35
|
+
# PATCH /boards/:id → boards#update
|
|
36
|
+
# DELETE /boards/:id → boards#destroy
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Limit Actions
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
resources :boards, only: %i[ index show create ]
|
|
43
|
+
resources :exports, only: %i[ create show ]
|
|
44
|
+
resources :tags, only: :index
|
|
45
|
+
|
|
46
|
+
resources :boards, except: %i[ destroy ]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Singleton Resources
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
resource :session # No :id in URL
|
|
53
|
+
# Generates:
|
|
54
|
+
# GET /session/new → sessions#new
|
|
55
|
+
# POST /session → sessions#create
|
|
56
|
+
# GET /session → sessions#show
|
|
57
|
+
# GET /session/edit → sessions#edit
|
|
58
|
+
# PATCH /session → sessions#update
|
|
59
|
+
# DELETE /session → sessions#destroy
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## The Core Pattern: Actions as Resources
|
|
65
|
+
|
|
66
|
+
### ❌ Bad - Custom Actions
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
resources :cards do
|
|
70
|
+
post :close
|
|
71
|
+
post :reopen
|
|
72
|
+
post :gild
|
|
73
|
+
post :ungild
|
|
74
|
+
post :pin
|
|
75
|
+
delete :unpin
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### ✅ Good - Actions as Resources
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
resources :cards do
|
|
83
|
+
scope module: :cards do
|
|
84
|
+
resource :closure # POST = close, DELETE = reopen
|
|
85
|
+
resource :goldness # POST = gild, DELETE = ungild
|
|
86
|
+
resource :pin # POST = pin, DELETE = unpin
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# URLs:
|
|
91
|
+
# POST /cards/:card_id/closure → Cards::ClosuresController#create
|
|
92
|
+
# DELETE /cards/:card_id/closure → Cards::ClosuresController#destroy
|
|
93
|
+
# POST /cards/:card_id/goldness → Cards::GoldnessesController#create
|
|
94
|
+
# DELETE /cards/:card_id/goldness → Cards::GoldnessesController#destroy
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Nested Resources
|
|
100
|
+
|
|
101
|
+
### Basic Nesting
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
resources :boards do
|
|
105
|
+
resources :cards
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Generates:
|
|
109
|
+
# GET /boards/:board_id/cards → cards#index
|
|
110
|
+
# POST /boards/:board_id/cards → cards#create
|
|
111
|
+
# GET /boards/:board_id/cards/:id → cards#show
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Shallow Nesting
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
resources :boards do
|
|
118
|
+
resources :cards, shallow: true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Generates:
|
|
122
|
+
# POST /boards/:board_id/cards → cards#create (nested)
|
|
123
|
+
# GET /cards/:id → cards#show (shallow)
|
|
124
|
+
# PATCH /cards/:id → cards#update (shallow)
|
|
125
|
+
# DELETE /cards/:id → cards#destroy (shallow)
|
|
126
|
+
|
|
127
|
+
# Why? Because cards/:id is enough to identify the resource
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Multiple Levels
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
resources :boards do
|
|
134
|
+
resources :cards do
|
|
135
|
+
resources :comments
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# But consider shallow nesting or limiting depth
|
|
140
|
+
resources :boards do
|
|
141
|
+
resources :cards, shallow: true do
|
|
142
|
+
resources :comments, shallow: true
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Organization with scope module:
|
|
150
|
+
|
|
151
|
+
### Keep URLs Clean
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Without scope module: (repetitive)
|
|
155
|
+
resources :cards do
|
|
156
|
+
resource :closure, controller: "cards/closures"
|
|
157
|
+
resource :goldness, controller: "cards/goldnesses"
|
|
158
|
+
resource :pin, controller: "cards/pins"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# With scope module: (clean)
|
|
162
|
+
resources :cards do
|
|
163
|
+
scope module: :cards do
|
|
164
|
+
resource :closure
|
|
165
|
+
resource :goldness
|
|
166
|
+
resource :pin
|
|
167
|
+
|
|
168
|
+
resources :comments
|
|
169
|
+
resources :taggings
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# URLs: /cards/:card_id/closure
|
|
174
|
+
# Controllers: Cards::ClosuresController
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Nested Scope Modules
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
resources :cards do
|
|
181
|
+
scope module: :cards do
|
|
182
|
+
resources :comments do
|
|
183
|
+
# Reactions belong to comments
|
|
184
|
+
resources :reactions, module: :comments
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# URL: /cards/:card_id/comments/:comment_id/reactions
|
|
190
|
+
# Controller: Cards::Comments::ReactionsController
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Namespace vs Scope Module
|
|
196
|
+
|
|
197
|
+
### namespace (affects both URL and controller)
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
namespace :admin do
|
|
201
|
+
resources :users
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# URL: /admin/users
|
|
205
|
+
# Controller: Admin::UsersController
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### scope module (controller only)
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
scope module: :cards do
|
|
212
|
+
resource :closure
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# URL: /closure
|
|
216
|
+
# Controller: Cards::ClosuresController
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### scope path (URL only)
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
scope path: :admin do
|
|
223
|
+
resources :users
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# URL: /admin/users
|
|
227
|
+
# Controller: UsersController
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Collection vs Member Routes
|
|
233
|
+
|
|
234
|
+
### Member Routes (act on specific resource)
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
resources :boards do
|
|
238
|
+
member do
|
|
239
|
+
post :archive # POST /boards/:id/archive
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Better: Model as resource
|
|
244
|
+
resources :boards do
|
|
245
|
+
resource :archive
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Collection Routes (act on collection)
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
resources :cards do
|
|
253
|
+
collection do
|
|
254
|
+
get :search # GET /cards/search
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Or use namespace
|
|
259
|
+
namespace :cards do
|
|
260
|
+
resources :searches
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Custom Routes
|
|
267
|
+
|
|
268
|
+
### Root Route
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
root "events#index"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Simple Routes
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
get "about", to: "pages#about"
|
|
278
|
+
post "contact", to: "contacts#create"
|
|
279
|
+
|
|
280
|
+
# With constraints
|
|
281
|
+
get "signup", to: "signups#new", constraints: { subdomain: "www" }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Direct Routes
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# Create helper method with custom URL
|
|
288
|
+
direct :published_board do |board, options|
|
|
289
|
+
route_for :public_board, board.publication.key
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Usage in views/controllers:
|
|
293
|
+
published_board_url(@board)
|
|
294
|
+
# => /b/abc123xyz
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Resolve Routes (Polymorphic)
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
# Map model to specific route
|
|
301
|
+
resolve "Event" do |event, options|
|
|
302
|
+
polymorphic_url(event.eventable, options)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
resolve "Comment" do |comment, options|
|
|
306
|
+
options[:anchor] = ActionView::RecordIdentifier.dom_id(comment)
|
|
307
|
+
route_for :card, comment.card, options
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Usage:
|
|
311
|
+
url_for(@event)
|
|
312
|
+
# => Routes to event.eventable (e.g., card_url(@event.eventable))
|
|
313
|
+
|
|
314
|
+
link_to "View", @comment
|
|
315
|
+
# => Routes to card with anchor #comment_123
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## RESTful Route Patterns
|
|
321
|
+
|
|
322
|
+
### Resource Routes (Complete Example)
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
resources :cards do
|
|
326
|
+
scope module: :cards do
|
|
327
|
+
# Singleton resources (toggles/state)
|
|
328
|
+
resource :board # Moving to different board
|
|
329
|
+
resource :closure # Closing/reopening
|
|
330
|
+
resource :column # Moving to column
|
|
331
|
+
resource :goldness # Gilding/ungilding
|
|
332
|
+
resource :image # Uploading/removing image
|
|
333
|
+
resource :not_now # Postponing
|
|
334
|
+
resource :pin # Pinning/unpinning
|
|
335
|
+
resource :publish # Publishing drafts
|
|
336
|
+
resource :triage # Moving to/from triage
|
|
337
|
+
resource :watch # Watching/unwatching
|
|
338
|
+
|
|
339
|
+
# Collection resources (CRUD)
|
|
340
|
+
resources :assignments
|
|
341
|
+
resources :steps
|
|
342
|
+
resources :taggings
|
|
343
|
+
resources :comments do
|
|
344
|
+
resources :reactions, module: :comments
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Boards Routes
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
resources :boards do
|
|
354
|
+
scope module: :boards do
|
|
355
|
+
# Singleton resources
|
|
356
|
+
resource :subscriptions
|
|
357
|
+
resource :involvement
|
|
358
|
+
resource :publication
|
|
359
|
+
resource :entropy
|
|
360
|
+
|
|
361
|
+
# Nested namespace
|
|
362
|
+
namespace :columns do
|
|
363
|
+
resource :not_now
|
|
364
|
+
resource :stream
|
|
365
|
+
resource :closed
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
resources :columns
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Only create action for cards (cards belong to boards)
|
|
372
|
+
resources :cards, only: :create
|
|
373
|
+
|
|
374
|
+
resources :webhooks do
|
|
375
|
+
scope module: :webhooks do
|
|
376
|
+
resource :activation, only: :create
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Route Constraints
|
|
385
|
+
|
|
386
|
+
### Parameter Constraints
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
resources :boards, constraints: { id: /\d+/ }
|
|
390
|
+
|
|
391
|
+
get "/:username", to: "profiles#show",
|
|
392
|
+
constraints: { username: /[a-zA-Z0-9_]+/ }
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Request Constraints
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
get "mobile", to: "pages#mobile",
|
|
399
|
+
constraints: { user_agent: /Mobile|Tablet/ }
|
|
400
|
+
|
|
401
|
+
namespace :admin, constraints: { subdomain: "admin" } do
|
|
402
|
+
resources :users
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Custom Constraint Classes
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
class AdminConstraint
|
|
410
|
+
def matches?(request)
|
|
411
|
+
user_id = request.session[:user_id]
|
|
412
|
+
user = User.find_by(id: user_id)
|
|
413
|
+
user && user.admin?
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
namespace :admin, constraints: AdminConstraint.new do
|
|
418
|
+
resources :users
|
|
419
|
+
end
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Route Concerns
|
|
425
|
+
|
|
426
|
+
### Define Reusable Route Patterns
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
concern :commentable do
|
|
430
|
+
resources :comments
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
concern :likeable do
|
|
434
|
+
resource :like
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Use in resources
|
|
438
|
+
resources :posts, concerns: %i[ commentable likeable ]
|
|
439
|
+
resources :articles, concerns: %i[ commentable likeable ]
|
|
440
|
+
|
|
441
|
+
# Equivalent to:
|
|
442
|
+
resources :posts do
|
|
443
|
+
resources :comments
|
|
444
|
+
resource :like
|
|
445
|
+
end
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Scope and Path Helpers
|
|
451
|
+
|
|
452
|
+
### Custom Path Names
|
|
453
|
+
|
|
454
|
+
```ruby
|
|
455
|
+
resources :boards, path: "collections"
|
|
456
|
+
# URL: /collections
|
|
457
|
+
# Helpers: boards_path, board_path
|
|
458
|
+
|
|
459
|
+
resource :session, path: "login"
|
|
460
|
+
# URL: /login
|
|
461
|
+
# Helper: session_path
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Custom Helper Names
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
resources :boards, as: :collections
|
|
468
|
+
# URL: /boards
|
|
469
|
+
# Helpers: collections_path, collection_path
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Route Helpers
|
|
475
|
+
|
|
476
|
+
### Path vs URL Helpers
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
boards_path # => "/boards"
|
|
480
|
+
boards_url # => "http://example.com/boards"
|
|
481
|
+
|
|
482
|
+
board_path(@board) # => "/boards/123"
|
|
483
|
+
board_url(@board) # => "http://example.com/boards/123"
|
|
484
|
+
|
|
485
|
+
new_board_path # => "/boards/new"
|
|
486
|
+
edit_board_path(@board) # => "/boards/123/edit"
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Nested Resource Helpers
|
|
490
|
+
|
|
491
|
+
```ruby
|
|
492
|
+
board_cards_path(@board) # => "/boards/123/cards"
|
|
493
|
+
board_card_path(@board, @card) # => "/boards/123/cards/456"
|
|
494
|
+
|
|
495
|
+
# With shallow: true
|
|
496
|
+
card_path(@card) # => "/cards/456"
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Custom Parameters
|
|
500
|
+
|
|
501
|
+
```ruby
|
|
502
|
+
board_path(@board, format: :json) # => "/boards/123.json"
|
|
503
|
+
boards_path(page: 2, per: 25) # => "/boards?page=2&per=25"
|
|
504
|
+
card_path(@card, anchor: "comments") # => "/cards/123#comments"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Multi-Tenancy with URL Path Scoping
|
|
510
|
+
|
|
511
|
+
### URL Structure
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
# Middleware extracts account_id from URL
|
|
515
|
+
# /{account_id}/boards/...
|
|
516
|
+
|
|
517
|
+
# In routes.rb - routes are defined normally
|
|
518
|
+
resources :boards
|
|
519
|
+
# But URLs become: /{account_id}/boards
|
|
520
|
+
|
|
521
|
+
# How it works:
|
|
522
|
+
# 1. Middleware (AccountSlug::Extractor) extracts account_id
|
|
523
|
+
# 2. Moves slug from PATH_INFO to SCRIPT_NAME
|
|
524
|
+
# 3. Rails thinks it's "mounted" at /{account_id}
|
|
525
|
+
# 4. All URL helpers automatically include account_id
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Untenanted Routes
|
|
529
|
+
|
|
530
|
+
```ruby
|
|
531
|
+
# For routes outside account context (login, signup)
|
|
532
|
+
scope :untenanted do
|
|
533
|
+
resource :session
|
|
534
|
+
resource :signup
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Or in controller:
|
|
538
|
+
def new
|
|
539
|
+
untenanted do
|
|
540
|
+
redirect_to new_session_url
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Redirect Routes
|
|
548
|
+
|
|
549
|
+
### Simple Redirects
|
|
550
|
+
|
|
551
|
+
```ruby
|
|
552
|
+
get "/old-path", to: redirect("/new-path")
|
|
553
|
+
get "/old-path", to: redirect("/new-path", status: 301) # Permanent
|
|
554
|
+
|
|
555
|
+
# With parameters
|
|
556
|
+
get "/articles/:id", to: redirect("/posts/%{id}")
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Dynamic Redirects
|
|
560
|
+
|
|
561
|
+
```ruby
|
|
562
|
+
# Legacy URLs
|
|
563
|
+
get "/collections/:collection_id/cards/:id",
|
|
564
|
+
to: redirect { |params, request|
|
|
565
|
+
"#{request.script_name}/cards/#{params[:id]}"
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
get "/collections/:id",
|
|
569
|
+
to: redirect { |params, request|
|
|
570
|
+
"#{request.script_name}/boards/#{params[:id]}"
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## API Versioning
|
|
577
|
+
|
|
578
|
+
### Namespace Versioning
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
namespace :api do
|
|
582
|
+
namespace :v1 do
|
|
583
|
+
resources :boards
|
|
584
|
+
resources :cards
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
namespace :v2 do
|
|
588
|
+
resources :boards
|
|
589
|
+
resources :cards
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# URLs: /api/v1/boards, /api/v2/boards
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Header-Based Versioning
|
|
597
|
+
|
|
598
|
+
```ruby
|
|
599
|
+
# Use constraints
|
|
600
|
+
scope module: :api do
|
|
601
|
+
scope module: :v1, constraints: ApiVersionConstraint.new(1) do
|
|
602
|
+
resources :boards
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
scope module: :v2, constraints: ApiVersionConstraint.new(2, default: true) do
|
|
606
|
+
resources :boards
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# ApiVersionConstraint class
|
|
611
|
+
class ApiVersionConstraint
|
|
612
|
+
def initialize(version, default: false)
|
|
613
|
+
@version = version
|
|
614
|
+
@default = default
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def matches?(request)
|
|
618
|
+
@default || request.headers["X-API-Version"] == "v#{@version}"
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Testing Routes
|
|
626
|
+
|
|
627
|
+
### Route Tests
|
|
628
|
+
|
|
629
|
+
```ruby
|
|
630
|
+
# test/routing/cards_routing_test.rb
|
|
631
|
+
class CardsRoutingTest < ActionDispatch::IntegrationTest
|
|
632
|
+
test "routes to cards#create" do
|
|
633
|
+
assert_routing(
|
|
634
|
+
{ method: :post, path: "/boards/1/cards" },
|
|
635
|
+
{ controller: "cards", action: "create", board_id: "1" }
|
|
636
|
+
)
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
test "routes to closure#create" do
|
|
640
|
+
assert_routing(
|
|
641
|
+
{ method: :post, path: "/cards/1/closure" },
|
|
642
|
+
{ controller: "cards/closures", action: "create", card_id: "1" }
|
|
643
|
+
)
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Route Helper Tests
|
|
649
|
+
|
|
650
|
+
```ruby
|
|
651
|
+
test "board_path generates correct path" do
|
|
652
|
+
board = boards(:writebook)
|
|
653
|
+
assert_equal "/boards/#{board.id}", board_path(board)
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
test "card_closure_path generates correct path" do
|
|
657
|
+
card = cards(:logo)
|
|
658
|
+
assert_equal "/cards/#{card.number}/closure", card_closure_path(card)
|
|
659
|
+
end
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Debugging Routes
|
|
665
|
+
|
|
666
|
+
### Rails Console
|
|
667
|
+
|
|
668
|
+
```ruby
|
|
669
|
+
# List all routes
|
|
670
|
+
Rails.application.routes.routes.map(&:path).map(&:spec).sort.uniq
|
|
671
|
+
|
|
672
|
+
# Find route by helper
|
|
673
|
+
Rails.application.routes.url_helpers.boards_path
|
|
674
|
+
# => "/boards"
|
|
675
|
+
|
|
676
|
+
# Find routes matching pattern
|
|
677
|
+
Rails.application.routes.routes.select { |r| r.path.spec.to_s =~ /cards/ }
|
|
678
|
+
|
|
679
|
+
# Route recognition
|
|
680
|
+
Rails.application.routes.recognize_path("/boards/123")
|
|
681
|
+
# => { controller: "boards", action: "show", id: "123" }
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### bin/rails routes
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
# All routes
|
|
688
|
+
bin/rails routes
|
|
689
|
+
|
|
690
|
+
# Filter by controller
|
|
691
|
+
bin/rails routes -c boards
|
|
692
|
+
|
|
693
|
+
# Filter by pattern
|
|
694
|
+
bin/rails routes -g closure
|
|
695
|
+
|
|
696
|
+
# Expanded format
|
|
697
|
+
bin/rails routes --expanded
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## Best Practices
|
|
703
|
+
|
|
704
|
+
### ✅ DO
|
|
705
|
+
|
|
706
|
+
1. **Use RESTful resources** - Standard CRUD actions
|
|
707
|
+
2. **Model actions as resources** - Singleton resources for toggles
|
|
708
|
+
3. **Use scope module:** - Keep URLs clean, controllers namespaced
|
|
709
|
+
4. **Shallow nesting** - Avoid deep nesting
|
|
710
|
+
5. **Limit actions** - Only include needed actions
|
|
711
|
+
6. **Use concerns** - DRY up repeated patterns
|
|
712
|
+
7. **Test routes** - Ensure correct routing
|
|
713
|
+
|
|
714
|
+
### ❌ DON'T
|
|
715
|
+
|
|
716
|
+
1. **Custom actions** - Use resources instead
|
|
717
|
+
2. **Deep nesting** - More than 2 levels
|
|
718
|
+
3. **Generic member/collection** - Use resources
|
|
719
|
+
4. **Mixing namespace and scope module** - Confusing
|
|
720
|
+
5. **Too many routes** - Keep focused
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Complete Example: Production Routes
|
|
725
|
+
|
|
726
|
+
```ruby
|
|
727
|
+
Rails.application.routes.draw do
|
|
728
|
+
root "events#index"
|
|
729
|
+
|
|
730
|
+
# Account settings
|
|
731
|
+
namespace :account do
|
|
732
|
+
resource :join_code
|
|
733
|
+
resource :settings
|
|
734
|
+
resource :entropy
|
|
735
|
+
resources :exports, only: %i[ create show ]
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
# Boards
|
|
739
|
+
resources :boards do
|
|
740
|
+
scope module: :boards do
|
|
741
|
+
resource :publication
|
|
742
|
+
resource :entropy
|
|
743
|
+
|
|
744
|
+
namespace :columns do
|
|
745
|
+
resource :not_now
|
|
746
|
+
resource :stream
|
|
747
|
+
resource :closed
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
resources :columns
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
resources :cards, only: :create
|
|
754
|
+
resources :webhooks
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Cards (main resource)
|
|
758
|
+
resources :cards do
|
|
759
|
+
scope module: :cards do
|
|
760
|
+
# Singleton resources (state toggles)
|
|
761
|
+
resource :closure
|
|
762
|
+
resource :goldness
|
|
763
|
+
resource :pin
|
|
764
|
+
resource :publish
|
|
765
|
+
|
|
766
|
+
# Collections
|
|
767
|
+
resources :comments do
|
|
768
|
+
resources :reactions, module: :comments
|
|
769
|
+
end
|
|
770
|
+
resources :assignments
|
|
771
|
+
resources :taggings
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
# Session
|
|
776
|
+
resource :session do
|
|
777
|
+
scope module: :sessions do
|
|
778
|
+
resource :magic_link
|
|
779
|
+
resource :menu
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# Direct routes
|
|
784
|
+
direct :published_board do |board|
|
|
785
|
+
route_for :public_board, board.publication.key
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
# Polymorphic resolution
|
|
789
|
+
resolve "Event" do |event, options|
|
|
790
|
+
polymorphic_url(event.eventable, options)
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
---
|
|
796
|
+
|
|
797
|
+
## Summary
|
|
798
|
+
|
|
799
|
+
- **RESTful Resources**: Everything is a resource
|
|
800
|
+
- **Actions as Resources**: Use singleton resources, not custom actions
|
|
801
|
+
- **Organization**: Use `scope module:` to keep URLs clean
|
|
802
|
+
- **Nesting**: Shallow nesting for deep relationships
|
|
803
|
+
- **Zero Custom Actions**: Model everything as resources
|
|
804
|
+
- **Clean URLs**: Predictable, standard REST patterns
|