rails_claude_skills 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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-controllers
|
|
3
|
+
description: Controller actions, routing, REST conventions, filters, and response handling
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
rails_version: ">= 7.0"
|
|
6
|
+
tags:
|
|
7
|
+
- controllers
|
|
8
|
+
- routing
|
|
9
|
+
- actions
|
|
10
|
+
- rest
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Rails Controllers
|
|
14
|
+
|
|
15
|
+
## Quick Reference
|
|
16
|
+
|
|
17
|
+
| Pattern | Example |
|
|
18
|
+
|---------|---------|
|
|
19
|
+
| **Generate** | `rails g controller Posts index show` |
|
|
20
|
+
| **Route** | `resources :posts` |
|
|
21
|
+
| **Action** | `def show; @post = Post.find(params[:id]); end` |
|
|
22
|
+
| **Render** | `render :edit` |
|
|
23
|
+
| **Redirect** | `redirect_to posts_path` |
|
|
24
|
+
| **Filter** | `before_action :authenticate_user!` |
|
|
25
|
+
| **Strong Params** | `params.require(:post).permit(:title, :body)` |
|
|
26
|
+
|
|
27
|
+
## Controller Structure
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class PostsController < ApplicationController
|
|
31
|
+
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
|
32
|
+
before_action :authenticate_user!, except: [:index, :show]
|
|
33
|
+
|
|
34
|
+
# GET /posts
|
|
35
|
+
def index
|
|
36
|
+
@posts = Post.all.order(created_at: :desc)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# GET /posts/:id
|
|
40
|
+
def show
|
|
41
|
+
# @post set by before_action
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# GET /posts/new
|
|
45
|
+
def new
|
|
46
|
+
@post = Post.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# POST /posts
|
|
50
|
+
def create
|
|
51
|
+
@post = Post.new(post_params)
|
|
52
|
+
|
|
53
|
+
if @post.save
|
|
54
|
+
redirect_to @post, notice: 'Post created successfully.'
|
|
55
|
+
else
|
|
56
|
+
render :new, status: :unprocessable_entity
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# GET /posts/:id/edit
|
|
61
|
+
def edit
|
|
62
|
+
# @post set by before_action
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# PATCH/PUT /posts/:id
|
|
66
|
+
def update
|
|
67
|
+
if @post.update(post_params)
|
|
68
|
+
redirect_to @post, notice: 'Post updated successfully.'
|
|
69
|
+
else
|
|
70
|
+
render :edit, status: :unprocessable_entity
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# DELETE /posts/:id
|
|
75
|
+
def destroy
|
|
76
|
+
@post.destroy
|
|
77
|
+
redirect_to posts_path, notice: 'Post deleted successfully.'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def set_post
|
|
83
|
+
@post = Post.find(params[:id])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def post_params
|
|
87
|
+
params.require(:post).permit(:title, :body, :published)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Routing
|
|
93
|
+
|
|
94
|
+
### RESTful Routes
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
# config/routes.rb
|
|
98
|
+
Rails.application.routes.draw do
|
|
99
|
+
# Creates 7 standard routes (index, show, new, create, edit, update, destroy)
|
|
100
|
+
resources :posts
|
|
101
|
+
|
|
102
|
+
# Limit actions
|
|
103
|
+
resources :posts, only: [:index, :show]
|
|
104
|
+
resources :posts, except: [:destroy]
|
|
105
|
+
|
|
106
|
+
# Nested resources
|
|
107
|
+
resources :authors do
|
|
108
|
+
resources :posts
|
|
109
|
+
end
|
|
110
|
+
# URLs: /authors/:author_id/posts
|
|
111
|
+
|
|
112
|
+
# Shallow nesting (recommended for deep nesting)
|
|
113
|
+
resources :authors do
|
|
114
|
+
resources :posts, shallow: true
|
|
115
|
+
end
|
|
116
|
+
# URLs: /authors/:author_id/posts (collection)
|
|
117
|
+
# /posts/:id (member)
|
|
118
|
+
|
|
119
|
+
# Custom member and collection routes
|
|
120
|
+
resources :posts do
|
|
121
|
+
member do
|
|
122
|
+
post :publish
|
|
123
|
+
post :unpublish
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
collection do
|
|
127
|
+
get :archived
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
# URLs: POST /posts/:id/publish
|
|
131
|
+
# GET /posts/archived
|
|
132
|
+
|
|
133
|
+
# Singular resource
|
|
134
|
+
resource :profile, only: [:show, :edit, :update]
|
|
135
|
+
# URLs: /profile (no :id needed)
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Custom Routes
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
# Named routes
|
|
143
|
+
get 'about', to: 'pages#about', as: :about
|
|
144
|
+
# Usage: about_path
|
|
145
|
+
|
|
146
|
+
# Root route
|
|
147
|
+
root 'posts#index'
|
|
148
|
+
|
|
149
|
+
# Redirect
|
|
150
|
+
get '/old-path', to: redirect('/new-path')
|
|
151
|
+
|
|
152
|
+
# Constraints
|
|
153
|
+
get 'posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
|
|
154
|
+
|
|
155
|
+
# Namespace
|
|
156
|
+
namespace :admin do
|
|
157
|
+
resources :posts
|
|
158
|
+
end
|
|
159
|
+
# URLs: /admin/posts
|
|
160
|
+
# Controller: Admin::PostsController
|
|
161
|
+
|
|
162
|
+
# Scope
|
|
163
|
+
scope module: 'admin' do
|
|
164
|
+
resources :posts
|
|
165
|
+
end
|
|
166
|
+
# URLs: /posts
|
|
167
|
+
# Controller: Admin::PostsController
|
|
168
|
+
|
|
169
|
+
# Concern for reusable routes
|
|
170
|
+
concern :commentable do
|
|
171
|
+
resources :comments
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
resources :posts, concerns: :commentable
|
|
175
|
+
resources :photos, concerns: :commentable
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Filters (Callbacks)
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
class ApplicationController < ActionController::Base
|
|
182
|
+
before_action :authenticate_user!
|
|
183
|
+
before_action :set_locale
|
|
184
|
+
around_action :log_request
|
|
185
|
+
after_action :track_analytics
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def set_locale
|
|
190
|
+
I18n.locale = params[:locale] || I18n.default_locale
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def log_request
|
|
194
|
+
start_time = Time.current
|
|
195
|
+
yield
|
|
196
|
+
duration = Time.current - start_time
|
|
197
|
+
Rails.logger.info "Request took #{duration}s"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class PostsController < ApplicationController
|
|
202
|
+
skip_before_action :authenticate_user!, only: [:index, :show]
|
|
203
|
+
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
|
204
|
+
before_action :authorize_post, only: [:edit, :update, :destroy]
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
def authorize_post
|
|
209
|
+
unless @post.author == current_user
|
|
210
|
+
redirect_to root_path, alert: 'Not authorized'
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Strong Parameters
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
class PostsController < ApplicationController
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
# Basic
|
|
223
|
+
def post_params
|
|
224
|
+
params.require(:post).permit(:title, :body, :published)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Arrays
|
|
228
|
+
def post_params
|
|
229
|
+
params.require(:post).permit(:title, :body, tag_ids: [])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Nested attributes
|
|
233
|
+
def post_params
|
|
234
|
+
params.require(:post).permit(
|
|
235
|
+
:title,
|
|
236
|
+
:body,
|
|
237
|
+
comments_attributes: [:id, :content, :_destroy]
|
|
238
|
+
)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Conditional
|
|
242
|
+
def post_params
|
|
243
|
+
permitted = [:title, :body]
|
|
244
|
+
permitted << :published if current_user.admin?
|
|
245
|
+
params.require(:post).permit(permitted)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Rendering and Redirecting
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
class PostsController < ApplicationController
|
|
254
|
+
def show
|
|
255
|
+
@post = Post.find(params[:id])
|
|
256
|
+
|
|
257
|
+
# Implicit render: renders views/posts/show.html.erb
|
|
258
|
+
|
|
259
|
+
# Explicit template
|
|
260
|
+
# render :show
|
|
261
|
+
|
|
262
|
+
# Different template
|
|
263
|
+
# render :custom_template
|
|
264
|
+
|
|
265
|
+
# Partial
|
|
266
|
+
# render partial: 'post', locals: { post: @post }
|
|
267
|
+
|
|
268
|
+
# JSON
|
|
269
|
+
# render json: @post
|
|
270
|
+
|
|
271
|
+
# Plain text
|
|
272
|
+
# render plain: "Hello"
|
|
273
|
+
|
|
274
|
+
# Status codes
|
|
275
|
+
# render :show, status: :ok
|
|
276
|
+
# render :new, status: :unprocessable_entity
|
|
277
|
+
# render json: { error: 'Not found' }, status: :not_found
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def create
|
|
281
|
+
@post = Post.new(post_params)
|
|
282
|
+
|
|
283
|
+
if @post.save
|
|
284
|
+
# Redirect to show page
|
|
285
|
+
redirect_to @post
|
|
286
|
+
|
|
287
|
+
# With flash message
|
|
288
|
+
# redirect_to @post, notice: 'Created!'
|
|
289
|
+
|
|
290
|
+
# With custom path
|
|
291
|
+
# redirect_to posts_path
|
|
292
|
+
|
|
293
|
+
# Back to previous page
|
|
294
|
+
# redirect_back(fallback_location: root_path)
|
|
295
|
+
else
|
|
296
|
+
render :new, status: :unprocessable_entity
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Flash Messages
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
class PostsController < ApplicationController
|
|
306
|
+
def create
|
|
307
|
+
@post = Post.new(post_params)
|
|
308
|
+
|
|
309
|
+
if @post.save
|
|
310
|
+
# Set flash for next request
|
|
311
|
+
flash[:notice] = 'Post created!'
|
|
312
|
+
# Or shorter:
|
|
313
|
+
redirect_to @post, notice: 'Post created!'
|
|
314
|
+
|
|
315
|
+
# Different flash types
|
|
316
|
+
# flash[:success] = 'Success!'
|
|
317
|
+
# flash[:error] = 'Error!'
|
|
318
|
+
# flash[:alert] = 'Alert!'
|
|
319
|
+
# flash[:warning] = 'Warning!'
|
|
320
|
+
else
|
|
321
|
+
# flash.now for current request
|
|
322
|
+
flash.now[:alert] = 'Could not create post'
|
|
323
|
+
render :new
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def update
|
|
328
|
+
# Keep flash for next request
|
|
329
|
+
flash.keep
|
|
330
|
+
redirect_to @post
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Response Formats
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
class PostsController < ApplicationController
|
|
339
|
+
def show
|
|
340
|
+
@post = Post.find(params[:id])
|
|
341
|
+
|
|
342
|
+
respond_to do |format|
|
|
343
|
+
format.html # renders show.html.erb
|
|
344
|
+
format.json { render json: @post }
|
|
345
|
+
format.xml { render xml: @post }
|
|
346
|
+
format.pdf { render pdf: generate_pdf(@post) }
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def create
|
|
351
|
+
@post = Post.new(post_params)
|
|
352
|
+
|
|
353
|
+
respond_to do |format|
|
|
354
|
+
if @post.save
|
|
355
|
+
format.html { redirect_to @post, notice: 'Created!' }
|
|
356
|
+
format.json { render json: @post, status: :created }
|
|
357
|
+
else
|
|
358
|
+
format.html { render :new, status: :unprocessable_entity }
|
|
359
|
+
format.json { render json: @post.errors, status: :unprocessable_entity }
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Controller Concerns
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
# app/controllers/concerns/authenticatable.rb
|
|
370
|
+
module Authenticatable
|
|
371
|
+
extend ActiveSupport::Concern
|
|
372
|
+
|
|
373
|
+
included do
|
|
374
|
+
before_action :authenticate_user!
|
|
375
|
+
helper_method :current_user, :logged_in?
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def current_user
|
|
379
|
+
@current_user ||= User.find_by(id: session[:user_id])
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def logged_in?
|
|
383
|
+
current_user.present?
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def authenticate_user!
|
|
387
|
+
unless logged_in?
|
|
388
|
+
redirect_to login_path, alert: 'Please log in'
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Usage
|
|
394
|
+
class PostsController < ApplicationController
|
|
395
|
+
include Authenticatable
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Error Handling
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
class ApplicationController < ActionController::Base
|
|
403
|
+
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
|
404
|
+
rescue_from ActionController::ParameterMissing, with: :parameter_missing
|
|
405
|
+
|
|
406
|
+
private
|
|
407
|
+
|
|
408
|
+
def record_not_found
|
|
409
|
+
render file: "#{Rails.root}/public/404.html", status: :not_found
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def parameter_missing
|
|
413
|
+
render json: { error: 'Missing parameter' }, status: :bad_request
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
class PostsController < ApplicationController
|
|
418
|
+
def show
|
|
419
|
+
@post = Post.find(params[:id])
|
|
420
|
+
rescue ActiveRecord::RecordNotFound
|
|
421
|
+
redirect_to posts_path, alert: 'Post not found'
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Session and Cookies
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
class SessionsController < ApplicationController
|
|
430
|
+
def create
|
|
431
|
+
user = User.find_by(email: params[:email])
|
|
432
|
+
|
|
433
|
+
if user&.authenticate(params[:password])
|
|
434
|
+
# Set session
|
|
435
|
+
session[:user_id] = user.id
|
|
436
|
+
|
|
437
|
+
# Set cookie
|
|
438
|
+
cookies[:user_name] = user.name
|
|
439
|
+
|
|
440
|
+
# Signed cookie (tamper-proof)
|
|
441
|
+
cookies.signed[:user_id] = user.id
|
|
442
|
+
|
|
443
|
+
# Encrypted cookie
|
|
444
|
+
cookies.encrypted[:user_data] = { id: user.id, role: user.role }
|
|
445
|
+
|
|
446
|
+
# Permanent cookie (20 years)
|
|
447
|
+
cookies.permanent[:remember_token] = user.remember_token
|
|
448
|
+
|
|
449
|
+
redirect_to root_path
|
|
450
|
+
else
|
|
451
|
+
flash.now[:alert] = 'Invalid credentials'
|
|
452
|
+
render :new
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def destroy
|
|
457
|
+
session.delete(:user_id)
|
|
458
|
+
cookies.delete(:user_name)
|
|
459
|
+
redirect_to root_path
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Best Practices
|
|
465
|
+
|
|
466
|
+
1. **Keep controllers thin** - Move business logic to models or service objects
|
|
467
|
+
2. **Use before_action** for common setup code
|
|
468
|
+
3. **Always use strong parameters** for security
|
|
469
|
+
4. **Return proper HTTP status codes**
|
|
470
|
+
5. **Use concerns** for shared controller behavior
|
|
471
|
+
6. **Follow REST conventions** when possible
|
|
472
|
+
7. **Handle errors gracefully** with rescue_from
|
|
473
|
+
8. **Use flash messages** for user feedback
|
|
474
|
+
9. **Set instance variables** only for view rendering
|
|
475
|
+
10. **Avoid complex queries** in controllers - use scopes or query objects
|
|
476
|
+
|
|
477
|
+
## Common Patterns
|
|
478
|
+
|
|
479
|
+
### Service Objects for Complex Actions
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
class PostsController < ApplicationController
|
|
483
|
+
def create
|
|
484
|
+
result = Posts::CreateService.call(
|
|
485
|
+
params: post_params,
|
|
486
|
+
user: current_user
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
if result.success?
|
|
490
|
+
redirect_to result.post, notice: 'Created!'
|
|
491
|
+
else
|
|
492
|
+
@post = result.post
|
|
493
|
+
flash.now[:alert] = result.error
|
|
494
|
+
render :new
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Query Objects for Complex Queries
|
|
501
|
+
|
|
502
|
+
```ruby
|
|
503
|
+
class PostsController < ApplicationController
|
|
504
|
+
def index
|
|
505
|
+
@posts = PostsQuery.new(params).call
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## References
|
|
511
|
+
|
|
512
|
+
- [Rails Guides - Controllers](https://guides.rubyonrails.org/action_controller_overview.html)
|
|
513
|
+
- [Rails Guides - Routing](https://guides.rubyonrails.org/routing.html)
|
|
514
|
+
- [Rails API - ActionController](https://api.rubyonrails.org/classes/ActionController/Base.html)
|