claude-agent-framework 1.0.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/README.md +128 -0
- package/bin/claude-framework +3 -0
- package/framework/agents/design-lead.md +240 -0
- package/framework/agents/product-owner.md +179 -0
- package/framework/agents/tech-lead.md +226 -0
- package/framework/commands/ayuda.md +127 -0
- package/framework/commands/a/303/261adir.md +98 -0
- package/framework/commands/backup.md +397 -0
- package/framework/commands/cambiar.md +110 -0
- package/framework/commands/cloud.md +457 -0
- package/framework/commands/code.md +142 -0
- package/framework/commands/debug.md +334 -0
- package/framework/commands/deploy.md +383 -0
- package/framework/commands/deshacer.md +120 -0
- package/framework/commands/estado.md +218 -0
- package/framework/commands/explica.md +227 -0
- package/framework/commands/feature.md +120 -0
- package/framework/commands/git.md +427 -0
- package/framework/commands/historial.md +202 -0
- package/framework/commands/learn.md +408 -0
- package/framework/commands/movil.md +245 -0
- package/framework/commands/nuevo.md +118 -0
- package/framework/commands/plan.md +134 -0
- package/framework/commands/prd.md +113 -0
- package/framework/commands/probar.md +148 -0
- package/framework/commands/revisar.md +208 -0
- package/framework/commands/seeds.md +230 -0
- package/framework/commands/seguridad.md +226 -0
- package/framework/commands/tasks.md +157 -0
- package/framework/skills/architecture/algorithms.md +970 -0
- package/framework/skills/architecture/clean-code.md +1080 -0
- package/framework/skills/architecture/design-patterns.md +1984 -0
- package/framework/skills/architecture/functional-programming.md +972 -0
- package/framework/skills/architecture/solid.md +991 -0
- package/framework/skills/cloud/cloud-aws.md +848 -0
- package/framework/skills/cloud/cloud-azure.md +931 -0
- package/framework/skills/cloud/cloud-gcp.md +848 -0
- package/framework/skills/cloud/message-queues.md +1229 -0
- package/framework/skills/core/accessibility.md +401 -0
- package/framework/skills/core/api.md +474 -0
- package/framework/skills/core/authentication.md +306 -0
- package/framework/skills/core/authorization.md +388 -0
- package/framework/skills/core/background-jobs.md +341 -0
- package/framework/skills/core/caching.md +473 -0
- package/framework/skills/core/code-review.md +341 -0
- package/framework/skills/core/controllers.md +290 -0
- package/framework/skills/core/cua.md +285 -0
- package/framework/skills/core/documentation.md +472 -0
- package/framework/skills/core/file-uploads.md +351 -0
- package/framework/skills/core/hotwire-native.md +296 -0
- package/framework/skills/core/hotwire.md +278 -0
- package/framework/skills/core/i18n.md +334 -0
- package/framework/skills/core/imports-exports.md +750 -0
- package/framework/skills/core/infrastructure.md +337 -0
- package/framework/skills/core/models.md +228 -0
- package/framework/skills/core/notifications.md +672 -0
- package/framework/skills/core/payments.md +581 -0
- package/framework/skills/core/performance.md +361 -0
- package/framework/skills/core/rails-scaffold.md +131 -0
- package/framework/skills/core/search.md +518 -0
- package/framework/skills/core/security.md +565 -0
- package/framework/skills/core/seeds.md +307 -0
- package/framework/skills/core/seo.md +542 -0
- package/framework/skills/core/testing.md +393 -0
- package/framework/skills/core/views.md +260 -0
- package/framework/skills/core/websockets.md +564 -0
- package/framework/skills/data/advanced-sql.md +1204 -0
- package/framework/skills/data/nosql.md +1141 -0
- package/framework/skills/devops/containers-advanced.md +1237 -0
- package/framework/skills/devops/debugging.md +834 -0
- package/framework/skills/devops/git-workflow.md +752 -0
- package/framework/skills/devops/networking.md +932 -0
- package/framework/skills/devops/shell-scripting.md +1132 -0
- package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
- package/framework/sub-agents/cloud-agent.md +677 -0
- package/framework/sub-agents/data.md +504 -0
- package/framework/sub-agents/debugging-agent.md +554 -0
- package/framework/sub-agents/devops.md +483 -0
- package/framework/sub-agents/docs.md +176 -0
- package/framework/sub-agents/frontend-dev.md +349 -0
- package/framework/sub-agents/git-workflow-agent.md +697 -0
- package/framework/sub-agents/integrations.md +630 -0
- package/framework/sub-agents/native-dev.md +434 -0
- package/framework/sub-agents/qa.md +138 -0
- package/framework/sub-agents/rails-dev.md +375 -0
- package/framework/sub-agents/security.md +526 -0
- package/framework/sub-agents/ui.md +437 -0
- package/framework/sub-agents/ux.md +284 -0
- package/framework/templates/api-spec.md +500 -0
- package/framework/templates/component-spec.md +248 -0
- package/framework/templates/feature.json +13 -0
- package/framework/templates/model-spec.md +318 -0
- package/framework/templates/prd-template.md +80 -0
- package/framework/templates/task-plan.md +122 -0
- package/framework/templates/task-user-story.md +52 -0
- package/framework/templates/technical-spec.md +260 -0
- package/framework/templates/user-story.md +95 -0
- package/package.json +42 -0
- package/project-templates/CLAUDE.md +42 -0
- package/project-templates/contexts/architecture.md +25 -0
- package/project-templates/contexts/conventions.md +46 -0
- package/project-templates/contexts/design-system.md +47 -0
- package/project-templates/contexts/requirements.md +38 -0
- package/project-templates/contexts/stack.md +30 -0
- package/project-templates/history/active/models.md +11 -0
- package/project-templates/history/changelog.md +15 -0
- package/project-templates/workspace/.gitkeep +0 -0
- package/src/cli.js +52 -0
- package/src/init.js +104 -0
- package/src/status.js +75 -0
- package/src/update.js +88 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Skill: Code Review
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Systematically review code for quality, security, performance, and maintainability.
|
|
5
|
+
|
|
6
|
+
## Review Checklist
|
|
7
|
+
|
|
8
|
+
### 1. Functionality
|
|
9
|
+
- [ ] Code does what it's supposed to do
|
|
10
|
+
- [ ] Edge cases are handled
|
|
11
|
+
- [ ] Error handling is appropriate
|
|
12
|
+
- [ ] No obvious bugs
|
|
13
|
+
|
|
14
|
+
### 2. Security
|
|
15
|
+
- [ ] No SQL injection vulnerabilities
|
|
16
|
+
- [ ] No XSS vulnerabilities
|
|
17
|
+
- [ ] Authentication/authorization checked
|
|
18
|
+
- [ ] Sensitive data not logged
|
|
19
|
+
- [ ] CSRF protection in place
|
|
20
|
+
- [ ] Strong parameters used
|
|
21
|
+
- [ ] No hardcoded secrets
|
|
22
|
+
|
|
23
|
+
### 3. Performance
|
|
24
|
+
- [ ] No N+1 queries
|
|
25
|
+
- [ ] Proper indexes exist
|
|
26
|
+
- [ ] No unnecessary database calls
|
|
27
|
+
- [ ] Efficient algorithms used
|
|
28
|
+
- [ ] Caching where appropriate
|
|
29
|
+
|
|
30
|
+
### 4. Code Quality
|
|
31
|
+
- [ ] Follows Rails conventions
|
|
32
|
+
- [ ] DRY - no unnecessary duplication
|
|
33
|
+
- [ ] Single responsibility principle
|
|
34
|
+
- [ ] Clear naming
|
|
35
|
+
- [ ] Appropriate comments (not excessive)
|
|
36
|
+
|
|
37
|
+
### 5. Testing
|
|
38
|
+
- [ ] Tests exist for new code
|
|
39
|
+
- [ ] Tests cover edge cases
|
|
40
|
+
- [ ] Tests are meaningful (not just coverage)
|
|
41
|
+
- [ ] All tests pass
|
|
42
|
+
|
|
43
|
+
### 6. Accessibility
|
|
44
|
+
- [ ] Semantic HTML used
|
|
45
|
+
- [ ] Form labels present
|
|
46
|
+
- [ ] ARIA attributes where needed
|
|
47
|
+
- [ ] Color contrast sufficient
|
|
48
|
+
|
|
49
|
+
## Security Review
|
|
50
|
+
|
|
51
|
+
### SQL Injection
|
|
52
|
+
```ruby
|
|
53
|
+
# BAD - SQL injection vulnerable
|
|
54
|
+
User.where("name = '#{params[:name]}'")
|
|
55
|
+
|
|
56
|
+
# GOOD - Parameterized
|
|
57
|
+
User.where(name: params[:name])
|
|
58
|
+
User.where("name = ?", params[:name])
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### XSS Prevention
|
|
62
|
+
```erb
|
|
63
|
+
<%# BAD - Raw HTML output %>
|
|
64
|
+
<%= raw @article.body %>
|
|
65
|
+
<%= @article.body.html_safe %>
|
|
66
|
+
|
|
67
|
+
<%# GOOD - Escaped by default %>
|
|
68
|
+
<%= @article.body %>
|
|
69
|
+
|
|
70
|
+
<%# GOOD - Sanitized HTML %>
|
|
71
|
+
<%= sanitize @article.body, tags: %w[p br strong em] %>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Mass Assignment
|
|
75
|
+
```ruby
|
|
76
|
+
# BAD - Permits everything
|
|
77
|
+
params.require(:user).permit!
|
|
78
|
+
|
|
79
|
+
# GOOD - Explicit whitelist
|
|
80
|
+
params.require(:user).permit(:name, :email)
|
|
81
|
+
|
|
82
|
+
# GOOD - Conditional permits
|
|
83
|
+
def user_params
|
|
84
|
+
permitted = [:name, :email]
|
|
85
|
+
permitted << :role if current_user.admin?
|
|
86
|
+
params.require(:user).permit(permitted)
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Authorization
|
|
91
|
+
```ruby
|
|
92
|
+
# BAD - No authorization check
|
|
93
|
+
def update
|
|
94
|
+
@article = Article.find(params[:id])
|
|
95
|
+
@article.update(article_params)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# GOOD - Ownership check
|
|
99
|
+
def update
|
|
100
|
+
@article = current_user.articles.find(params[:id])
|
|
101
|
+
@article.update(article_params)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# GOOD - Policy check
|
|
105
|
+
def update
|
|
106
|
+
@article = Article.find(params[:id])
|
|
107
|
+
authorize @article
|
|
108
|
+
@article.update(article_params)
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Performance Review
|
|
113
|
+
|
|
114
|
+
### N+1 Queries
|
|
115
|
+
```ruby
|
|
116
|
+
# BAD - N+1 query
|
|
117
|
+
@articles = Article.all
|
|
118
|
+
@articles.each { |a| puts a.user.name }
|
|
119
|
+
|
|
120
|
+
# GOOD - Eager loading
|
|
121
|
+
@articles = Article.includes(:user)
|
|
122
|
+
@articles.each { |a| puts a.user.name }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Efficient Queries
|
|
126
|
+
```ruby
|
|
127
|
+
# BAD - Loads all records
|
|
128
|
+
User.all.count
|
|
129
|
+
User.all.any?
|
|
130
|
+
|
|
131
|
+
# GOOD - Database operations
|
|
132
|
+
User.count
|
|
133
|
+
User.exists?
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Avoid Memory Bloat
|
|
137
|
+
```ruby
|
|
138
|
+
# BAD - Loads all into memory
|
|
139
|
+
User.all.each { |u| process(u) }
|
|
140
|
+
|
|
141
|
+
# GOOD - Batch processing
|
|
142
|
+
User.find_each { |u| process(u) }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Code Quality Review
|
|
146
|
+
|
|
147
|
+
### Single Responsibility
|
|
148
|
+
```ruby
|
|
149
|
+
# BAD - Controller doing too much
|
|
150
|
+
def create
|
|
151
|
+
@user = User.new(user_params)
|
|
152
|
+
@user.generate_token
|
|
153
|
+
@user.save
|
|
154
|
+
UserMailer.welcome(@user).deliver_now
|
|
155
|
+
Analytics.track("signup", @user.id)
|
|
156
|
+
redirect_to dashboard_path
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# GOOD - Delegated to service/job
|
|
160
|
+
def create
|
|
161
|
+
@user = User.new(user_params)
|
|
162
|
+
if @user.save
|
|
163
|
+
UserRegistrationJob.perform_later(@user.id)
|
|
164
|
+
redirect_to dashboard_path
|
|
165
|
+
else
|
|
166
|
+
render :new
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Clear Naming
|
|
172
|
+
```ruby
|
|
173
|
+
# BAD - Unclear names
|
|
174
|
+
def process(d)
|
|
175
|
+
d.map { |x| x * 2 }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# GOOD - Descriptive names
|
|
179
|
+
def double_quantities(order_items)
|
|
180
|
+
order_items.map { |item| item.quantity * 2 }
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Avoid Deep Nesting
|
|
185
|
+
```ruby
|
|
186
|
+
# BAD - Too nested
|
|
187
|
+
if user
|
|
188
|
+
if user.active?
|
|
189
|
+
if user.verified?
|
|
190
|
+
do_something
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# GOOD - Early returns
|
|
196
|
+
return unless user
|
|
197
|
+
return unless user.active?
|
|
198
|
+
return unless user.verified?
|
|
199
|
+
do_something
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Rails Conventions Review
|
|
203
|
+
|
|
204
|
+
### RESTful Actions
|
|
205
|
+
```ruby
|
|
206
|
+
# BAD - Non-RESTful action names
|
|
207
|
+
def fetch_articles
|
|
208
|
+
def do_publish
|
|
209
|
+
|
|
210
|
+
# GOOD - RESTful
|
|
211
|
+
def index
|
|
212
|
+
def show
|
|
213
|
+
def publish # Custom action on member
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Callbacks vs Explicit Calls
|
|
217
|
+
```ruby
|
|
218
|
+
# QUESTIONABLE - Hidden behavior
|
|
219
|
+
class Article < ApplicationRecord
|
|
220
|
+
after_save :notify_subscribers
|
|
221
|
+
after_save :update_search_index
|
|
222
|
+
after_save :clear_cache
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# BETTER - Explicit service object
|
|
226
|
+
class ArticlePublisher
|
|
227
|
+
def call(article)
|
|
228
|
+
article.save!
|
|
229
|
+
notify_subscribers(article)
|
|
230
|
+
update_search_index(article)
|
|
231
|
+
clear_cache(article)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Fat Models, Skinny Controllers
|
|
237
|
+
```ruby
|
|
238
|
+
# BAD - Logic in controller
|
|
239
|
+
def create
|
|
240
|
+
@order = Order.new(order_params)
|
|
241
|
+
@order.total = @order.items.sum(&:price) * 1.1 # Tax
|
|
242
|
+
@order.status = "pending"
|
|
243
|
+
@order.save
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# GOOD - Logic in model
|
|
247
|
+
def create
|
|
248
|
+
@order = Order.new(order_params)
|
|
249
|
+
@order.save
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class Order < ApplicationRecord
|
|
253
|
+
before_save :calculate_total
|
|
254
|
+
|
|
255
|
+
def calculate_total
|
|
256
|
+
self.total = items.sum(&:price) * tax_rate
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Review Report Format
|
|
262
|
+
|
|
263
|
+
```markdown
|
|
264
|
+
# Code Review: [PR/Feature Name]
|
|
265
|
+
Reviewer: [Name]
|
|
266
|
+
Date: [Date]
|
|
267
|
+
|
|
268
|
+
## Summary
|
|
269
|
+
[Brief description of what was reviewed]
|
|
270
|
+
|
|
271
|
+
## Findings
|
|
272
|
+
|
|
273
|
+
### Critical Issues
|
|
274
|
+
1. **[Issue Title]**
|
|
275
|
+
- File: `path/to/file.rb:123`
|
|
276
|
+
- Problem: [Description]
|
|
277
|
+
- Suggestion: [How to fix]
|
|
278
|
+
|
|
279
|
+
### Warnings
|
|
280
|
+
1. **[Issue Title]**
|
|
281
|
+
- File: `path/to/file.rb:456`
|
|
282
|
+
- Problem: [Description]
|
|
283
|
+
- Suggestion: [How to fix]
|
|
284
|
+
|
|
285
|
+
### Suggestions
|
|
286
|
+
1. **[Improvement]**
|
|
287
|
+
- File: `path/to/file.rb:789`
|
|
288
|
+
- Current: [Current approach]
|
|
289
|
+
- Suggested: [Better approach]
|
|
290
|
+
|
|
291
|
+
## Positive Notes
|
|
292
|
+
- [What was done well]
|
|
293
|
+
- [Good patterns used]
|
|
294
|
+
|
|
295
|
+
## Tests
|
|
296
|
+
- [ ] Tests exist and pass
|
|
297
|
+
- [ ] Coverage adequate
|
|
298
|
+
|
|
299
|
+
## Verdict
|
|
300
|
+
- [ ] Approved
|
|
301
|
+
- [ ] Approved with minor changes
|
|
302
|
+
- [ ] Changes requested
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Automated Checks
|
|
306
|
+
|
|
307
|
+
### Linting (RuboCop)
|
|
308
|
+
```yaml
|
|
309
|
+
# .rubocop.yml
|
|
310
|
+
require:
|
|
311
|
+
- rubocop-rails
|
|
312
|
+
- rubocop-rspec
|
|
313
|
+
|
|
314
|
+
AllCops:
|
|
315
|
+
NewCops: enable
|
|
316
|
+
TargetRubyVersion: 3.3
|
|
317
|
+
|
|
318
|
+
Rails:
|
|
319
|
+
Enabled: true
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Security Scanning (Brakeman)
|
|
323
|
+
```bash
|
|
324
|
+
# Run security scan
|
|
325
|
+
brakeman -o brakeman-report.html
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Dependency Audit
|
|
329
|
+
```bash
|
|
330
|
+
# Check for vulnerable gems
|
|
331
|
+
bundle audit check --update
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Best Practices
|
|
335
|
+
|
|
336
|
+
1. **Review small PRs** - Easier to catch issues
|
|
337
|
+
2. **Use automated tools first** - Let robots catch obvious issues
|
|
338
|
+
3. **Focus on logic** - Don't nitpick style (let linters handle it)
|
|
339
|
+
4. **Ask questions** - Don't assume, clarify intent
|
|
340
|
+
5. **Be constructive** - Suggest solutions, not just problems
|
|
341
|
+
6. **Prioritize security** - Always check auth and data handling
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Skill: Controllers
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Create RESTful controllers with proper authentication, authorization, parameter handling, and responses.
|
|
5
|
+
|
|
6
|
+
## Controller Structure
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
class ArticlesController < ApplicationController
|
|
10
|
+
# 1. Callbacks
|
|
11
|
+
before_action :authenticate_user!
|
|
12
|
+
before_action :set_article, only: %i[show edit update destroy]
|
|
13
|
+
|
|
14
|
+
# 2. RESTful actions
|
|
15
|
+
def index
|
|
16
|
+
@articles = Article.includes(:user).recent.page(params[:page])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def show
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def new
|
|
23
|
+
@article = current_user.articles.build
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create
|
|
27
|
+
@article = current_user.articles.build(article_params)
|
|
28
|
+
|
|
29
|
+
if @article.save
|
|
30
|
+
redirect_to @article, notice: "Article created successfully."
|
|
31
|
+
else
|
|
32
|
+
render :new, status: :unprocessable_entity
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def edit
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update
|
|
40
|
+
if @article.update(article_params)
|
|
41
|
+
redirect_to @article, notice: "Article updated successfully."
|
|
42
|
+
else
|
|
43
|
+
render :edit, status: :unprocessable_entity
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def destroy
|
|
48
|
+
@article.destroy
|
|
49
|
+
redirect_to articles_path, notice: "Article deleted."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def set_article
|
|
55
|
+
@article = Article.find(params[:id])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def article_params
|
|
59
|
+
params.require(:article).permit(:title, :body, :published)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Strong Parameters
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# Basic
|
|
68
|
+
def article_params
|
|
69
|
+
params.require(:article).permit(:title, :body)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# With nested attributes
|
|
73
|
+
def article_params
|
|
74
|
+
params.require(:article).permit(
|
|
75
|
+
:title,
|
|
76
|
+
:body,
|
|
77
|
+
tags_attributes: [:id, :name, :_destroy],
|
|
78
|
+
images: [] # Array of files
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Conditional
|
|
83
|
+
def article_params
|
|
84
|
+
permitted = [:title, :body]
|
|
85
|
+
permitted << :published if current_user.admin?
|
|
86
|
+
params.require(:article).permit(permitted)
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Before Actions
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
class ArticlesController < ApplicationController
|
|
94
|
+
# Authentication
|
|
95
|
+
before_action :authenticate_user!
|
|
96
|
+
before_action :authenticate_user!, except: [:index, :show]
|
|
97
|
+
|
|
98
|
+
# Resource loading
|
|
99
|
+
before_action :set_article, only: %i[show edit update destroy]
|
|
100
|
+
|
|
101
|
+
# Authorization
|
|
102
|
+
before_action :authorize_article, only: %i[edit update destroy]
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def set_article
|
|
107
|
+
@article = Article.find(params[:id])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def authorize_article
|
|
111
|
+
redirect_to articles_path, alert: "Not authorized" unless @article.user == current_user
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Response Formats
|
|
117
|
+
|
|
118
|
+
### HTML with Turbo
|
|
119
|
+
```ruby
|
|
120
|
+
def create
|
|
121
|
+
@article = current_user.articles.build(article_params)
|
|
122
|
+
|
|
123
|
+
if @article.save
|
|
124
|
+
respond_to do |format|
|
|
125
|
+
format.html { redirect_to @article, notice: "Created!" }
|
|
126
|
+
format.turbo_stream
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
render :new, status: :unprocessable_entity
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### JSON API
|
|
135
|
+
```ruby
|
|
136
|
+
def index
|
|
137
|
+
@articles = Article.all
|
|
138
|
+
|
|
139
|
+
respond_to do |format|
|
|
140
|
+
format.html
|
|
141
|
+
format.json { render json: @articles }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Flash Messages
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
# Standard messages
|
|
150
|
+
redirect_to @article, notice: "Article created."
|
|
151
|
+
redirect_to articles_path, alert: "Something went wrong."
|
|
152
|
+
|
|
153
|
+
# Custom flash types
|
|
154
|
+
flash[:success] = "Great job!"
|
|
155
|
+
flash.now[:error] = "Please fix the errors." # For render
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Turbo Stream Responses
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# app/views/articles/create.turbo_stream.erb
|
|
162
|
+
<%= turbo_stream.prepend "articles", @article %>
|
|
163
|
+
<%= turbo_stream.update "flash", partial: "shared/flash" %>
|
|
164
|
+
|
|
165
|
+
# Or inline in controller
|
|
166
|
+
def create
|
|
167
|
+
@article = current_user.articles.build(article_params)
|
|
168
|
+
|
|
169
|
+
if @article.save
|
|
170
|
+
render turbo_stream: [
|
|
171
|
+
turbo_stream.prepend("articles", @article),
|
|
172
|
+
turbo_stream.update("form", partial: "form", locals: { article: Article.new })
|
|
173
|
+
]
|
|
174
|
+
else
|
|
175
|
+
render :new, status: :unprocessable_entity
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Authorization with Pundit
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
class ArticlesController < ApplicationController
|
|
184
|
+
include Pundit::Authorization
|
|
185
|
+
|
|
186
|
+
def show
|
|
187
|
+
@article = Article.find(params[:id])
|
|
188
|
+
authorize @article
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def create
|
|
192
|
+
@article = current_user.articles.build(article_params)
|
|
193
|
+
authorize @article
|
|
194
|
+
|
|
195
|
+
if @article.save
|
|
196
|
+
redirect_to @article
|
|
197
|
+
else
|
|
198
|
+
render :new, status: :unprocessable_entity
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Error Handling
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
class ApplicationController < ActionController::Base
|
|
208
|
+
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
|
209
|
+
rescue_from Pundit::NotAuthorizedError, with: :forbidden
|
|
210
|
+
|
|
211
|
+
private
|
|
212
|
+
|
|
213
|
+
def not_found
|
|
214
|
+
render file: Rails.root.join("public/404.html"), status: :not_found, layout: false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def forbidden
|
|
218
|
+
redirect_to root_path, alert: "You are not authorized to perform this action."
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## RESTful Custom Actions
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
# routes.rb
|
|
227
|
+
resources :articles do
|
|
228
|
+
member do
|
|
229
|
+
patch :publish
|
|
230
|
+
patch :archive
|
|
231
|
+
end
|
|
232
|
+
collection do
|
|
233
|
+
get :drafts
|
|
234
|
+
get :search
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# controller
|
|
239
|
+
def publish
|
|
240
|
+
@article = Article.find(params[:id])
|
|
241
|
+
@article.update(published: true)
|
|
242
|
+
redirect_to @article, notice: "Article published."
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def drafts
|
|
246
|
+
@articles = current_user.articles.where(published: false)
|
|
247
|
+
render :index
|
|
248
|
+
end
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Pagination
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# With Pagy (recommended for Rails 8)
|
|
255
|
+
class ArticlesController < ApplicationController
|
|
256
|
+
include Pagy::Backend
|
|
257
|
+
|
|
258
|
+
def index
|
|
259
|
+
@pagy, @articles = pagy(Article.recent, items: 20)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# In view
|
|
264
|
+
<%== pagy_nav(@pagy) %>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Concerns
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# app/controllers/concerns/searchable.rb
|
|
271
|
+
module Searchable
|
|
272
|
+
extend ActiveSupport::Concern
|
|
273
|
+
|
|
274
|
+
def search
|
|
275
|
+
@results = model_class.search(params[:q])
|
|
276
|
+
render :index
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
def model_class
|
|
282
|
+
controller_name.classify.constantize
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Usage
|
|
287
|
+
class ArticlesController < ApplicationController
|
|
288
|
+
include Searchable
|
|
289
|
+
end
|
|
290
|
+
```
|