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,456 @@
|
|
|
1
|
+
# JSON Serialization Patterns
|
|
2
|
+
|
|
3
|
+
Control how your models are serialized to JSON for API responses. Choose the right serialization approach based on your needs.
|
|
4
|
+
|
|
5
|
+
## Default Rails Serialization
|
|
6
|
+
|
|
7
|
+
Rails provides basic JSON serialization out of the box:
|
|
8
|
+
|
|
9
|
+
### as_json / to_json
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# In controller
|
|
13
|
+
def show
|
|
14
|
+
render json: @article
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Customize in model
|
|
18
|
+
class Article < ApplicationRecord
|
|
19
|
+
def as_json(options = {})
|
|
20
|
+
super(
|
|
21
|
+
only: [:id, :title, :body, :status],
|
|
22
|
+
include: {
|
|
23
|
+
author: { only: [:id, :name, :email] },
|
|
24
|
+
comments: { only: [:id, :body, :created_at] }
|
|
25
|
+
},
|
|
26
|
+
methods: [:word_count]
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def word_count
|
|
31
|
+
body.split.size
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Pros:** Built-in, no dependencies
|
|
37
|
+
**Cons:** Mixes presentation logic with models, not reusable
|
|
38
|
+
|
|
39
|
+
## Jbuilder (Rails Default)
|
|
40
|
+
|
|
41
|
+
Jbuilder uses view templates for JSON responses:
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# app/views/api/v1/articles/show.json.jbuilder
|
|
47
|
+
json.article do
|
|
48
|
+
json.id @article.id
|
|
49
|
+
json.title @article.title
|
|
50
|
+
json.body @article.body
|
|
51
|
+
json.status @article.status
|
|
52
|
+
json.created_at @article.created_at
|
|
53
|
+
|
|
54
|
+
json.author do
|
|
55
|
+
json.id @article.author.id
|
|
56
|
+
json.name @article.author.name
|
|
57
|
+
json.email @article.author.email
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Collections
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# app/views/api/v1/articles/index.json.jbuilder
|
|
66
|
+
json.data @articles do |article|
|
|
67
|
+
json.id article.id
|
|
68
|
+
json.title article.title
|
|
69
|
+
json.excerpt article.body.truncate(100)
|
|
70
|
+
json.author_name article.author.name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
json.meta do
|
|
74
|
+
json.current_page @articles.current_page
|
|
75
|
+
json.total_pages @articles.total_pages
|
|
76
|
+
json.total_count @articles.total_count
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Partials
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# app/views/api/v1/articles/_article.json.jbuilder
|
|
84
|
+
json.extract! article, :id, :title, :body, :status, :created_at
|
|
85
|
+
|
|
86
|
+
json.author do
|
|
87
|
+
json.partial! 'api/v1/users/user', user: article.author
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# app/views/api/v1/articles/show.json.jbuilder
|
|
91
|
+
json.article do
|
|
92
|
+
json.partial! 'article', article: @article
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Pros:** Familiar view-based approach, good for complex nested data
|
|
97
|
+
**Cons:** Slower than other options, requires view files
|
|
98
|
+
|
|
99
|
+
## ActiveModelSerializers (AMS)
|
|
100
|
+
|
|
101
|
+
Class-based serializers with conventions:
|
|
102
|
+
|
|
103
|
+
### Installation
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Gemfile
|
|
107
|
+
gem 'active_model_serializers', '~> 0.10.0'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Basic Serializer
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# app/serializers/article_serializer.rb
|
|
114
|
+
class ArticleSerializer < ActiveModel::Serializer
|
|
115
|
+
attributes :id, :title, :body, :status, :created_at, :word_count
|
|
116
|
+
|
|
117
|
+
belongs_to :author
|
|
118
|
+
has_many :comments
|
|
119
|
+
|
|
120
|
+
def word_count
|
|
121
|
+
object.body.split.size
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# app/serializers/author_serializer.rb
|
|
126
|
+
class AuthorSerializer < ActiveModel::Serializer
|
|
127
|
+
attributes :id, :name, :email
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Controller automatically uses serializer
|
|
131
|
+
def show
|
|
132
|
+
render json: @article # Uses ArticleSerializer
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Conditional Attributes
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
class ArticleSerializer < ActiveModel::Serializer
|
|
140
|
+
attributes :id, :title, :body, :draft_notes
|
|
141
|
+
|
|
142
|
+
def draft_notes
|
|
143
|
+
object.draft_notes if object.status == 'draft'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def include_draft_notes?
|
|
147
|
+
object.status == 'draft'
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Custom Serializers
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# Use specific serializer
|
|
156
|
+
render json: @article, serializer: ArticleDetailSerializer
|
|
157
|
+
|
|
158
|
+
# Disable serializer
|
|
159
|
+
render json: @article, serializer: nil
|
|
160
|
+
|
|
161
|
+
# Collection with meta
|
|
162
|
+
render json: @articles,
|
|
163
|
+
meta: { total_count: @articles.total_count },
|
|
164
|
+
meta_key: 'pagination'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Pros:** Convention over configuration, automatic associations
|
|
168
|
+
**Cons:** Slower than simpler alternatives, less active maintenance
|
|
169
|
+
|
|
170
|
+
## Blueprinter (Recommended)
|
|
171
|
+
|
|
172
|
+
Fast, declarative serialization with great performance:
|
|
173
|
+
|
|
174
|
+
### Installation
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# Gemfile
|
|
178
|
+
gem 'blueprinter'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Basic Blueprint
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# app/blueprints/article_blueprint.rb
|
|
185
|
+
class ArticleBlueprint < Blueprinter::Base
|
|
186
|
+
identifier :id
|
|
187
|
+
|
|
188
|
+
fields :title, :body, :status, :created_at
|
|
189
|
+
|
|
190
|
+
field :word_count do |article|
|
|
191
|
+
article.body.split.size
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
association :author, blueprint: AuthorBlueprint
|
|
195
|
+
association :comments, blueprint: CommentBlueprint
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# app/blueprints/author_blueprint.rb
|
|
199
|
+
class AuthorBlueprint < Blueprinter::Base
|
|
200
|
+
identifier :id
|
|
201
|
+
fields :name, :email
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# In controller
|
|
205
|
+
def show
|
|
206
|
+
render json: ArticleBlueprint.render(@article)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def index
|
|
210
|
+
render json: ArticleBlueprint.render(@articles)
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Views (Different Representations)
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
class ArticleBlueprint < Blueprinter::Base
|
|
218
|
+
identifier :id
|
|
219
|
+
|
|
220
|
+
# Default view
|
|
221
|
+
fields :title, :status, :created_at
|
|
222
|
+
|
|
223
|
+
# Extended view with more fields
|
|
224
|
+
view :extended do
|
|
225
|
+
fields :body, :updated_at
|
|
226
|
+
association :author, blueprint: AuthorBlueprint
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Summary view with minimal fields
|
|
230
|
+
view :summary do
|
|
231
|
+
field :title
|
|
232
|
+
field :excerpt do |article|
|
|
233
|
+
article.body.truncate(100)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Usage
|
|
239
|
+
ArticleBlueprint.render(@article) # Default view
|
|
240
|
+
ArticleBlueprint.render(@article, view: :extended) # Extended view
|
|
241
|
+
ArticleBlueprint.render(@articles, view: :summary) # Summary for list
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Conditional Fields
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
class ArticleBlueprint < Blueprinter::Base
|
|
248
|
+
fields :id, :title, :body
|
|
249
|
+
|
|
250
|
+
field :draft_notes, if: ->(article, _options) { article.status == 'draft' }
|
|
251
|
+
|
|
252
|
+
field :edit_url, if: ->(article, options) {
|
|
253
|
+
options[:current_user]&.can_edit?(article)
|
|
254
|
+
}
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Usage
|
|
258
|
+
ArticleBlueprint.render(@article, current_user: current_user)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Meta and Root
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# With meta
|
|
265
|
+
render json: ArticleBlueprint.render(@articles, meta: {
|
|
266
|
+
total_count: @articles.total_count,
|
|
267
|
+
current_page: @articles.current_page
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
# Output:
|
|
271
|
+
# {
|
|
272
|
+
# "data": [...articles...],
|
|
273
|
+
# "meta": { "total_count": 42, "current_page": 1 }
|
|
274
|
+
# }
|
|
275
|
+
|
|
276
|
+
# Custom root
|
|
277
|
+
ArticleBlueprint.render(@articles, root: :articles)
|
|
278
|
+
# { "articles": [...] }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Pros:** Fast (5-10x faster than AMS), simple, flexible views
|
|
282
|
+
**Cons:** Less magic, more explicit
|
|
283
|
+
|
|
284
|
+
## JSONAPI::Serializer (formerly fast_jsonapi)
|
|
285
|
+
|
|
286
|
+
JSON:API specification compliant serialization:
|
|
287
|
+
|
|
288
|
+
### Installation
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
# Gemfile
|
|
292
|
+
gem 'jsonapi-serializer'
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Basic Serializer
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
# app/serializers/article_serializer.rb
|
|
299
|
+
class ArticleSerializer
|
|
300
|
+
include JSONAPI::Serializer
|
|
301
|
+
|
|
302
|
+
attributes :title, :body, :status, :created_at
|
|
303
|
+
|
|
304
|
+
attribute :word_count do |article|
|
|
305
|
+
article.body.split.size
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
belongs_to :author
|
|
309
|
+
has_many :comments
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# In controller
|
|
313
|
+
def show
|
|
314
|
+
render json: ArticleSerializer.new(@article).serializable_hash
|
|
315
|
+
end
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### JSON:API Response Format
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"data": {
|
|
323
|
+
"id": "1",
|
|
324
|
+
"type": "article",
|
|
325
|
+
"attributes": {
|
|
326
|
+
"title": "Rails API Guide",
|
|
327
|
+
"body": "Content here...",
|
|
328
|
+
"status": "published",
|
|
329
|
+
"created_at": "2025-01-15T10:00:00Z"
|
|
330
|
+
},
|
|
331
|
+
"relationships": {
|
|
332
|
+
"author": {
|
|
333
|
+
"data": { "id": "5", "type": "user" }
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"included": [
|
|
338
|
+
{
|
|
339
|
+
"id": "5",
|
|
340
|
+
"type": "user",
|
|
341
|
+
"attributes": {
|
|
342
|
+
"name": "John Doe"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### With Includes
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
# Include related resources
|
|
353
|
+
options = { include: [:author, :comments] }
|
|
354
|
+
ArticleSerializer.new(@article, options).serializable_hash
|
|
355
|
+
|
|
356
|
+
# Conditional includes based on params
|
|
357
|
+
def show
|
|
358
|
+
options = {}
|
|
359
|
+
options[:include] = params[:include].split(',') if params[:include].present?
|
|
360
|
+
|
|
361
|
+
render json: ArticleSerializer.new(@article, options).serializable_hash
|
|
362
|
+
end
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Pros:** JSON:API compliant, fast, handles includes well
|
|
366
|
+
**Cons:** Opinionated format, more verbose responses
|
|
367
|
+
|
|
368
|
+
## Comparison Table
|
|
369
|
+
|
|
370
|
+
| Gem | Speed | Flexibility | Learning Curve | Use Case |
|
|
371
|
+
|-----|-------|-------------|----------------|----------|
|
|
372
|
+
| **as_json** | Fast | Low | Easy | Simple APIs, prototypes |
|
|
373
|
+
| **Jbuilder** | Slow | High | Easy | Complex nested data, familiar views |
|
|
374
|
+
| **AMS** | Slow | Medium | Medium | Rails conventions, associations |
|
|
375
|
+
| **Blueprinter** | Very Fast | High | Easy | Production APIs, performance-critical |
|
|
376
|
+
| **JSONAPI::Serializer** | Fast | Medium | Medium | JSON:API clients, standardized APIs |
|
|
377
|
+
|
|
378
|
+
## Recommendation by Project Size
|
|
379
|
+
|
|
380
|
+
### Small Projects / Prototypes
|
|
381
|
+
Use Rails default `as_json` or Jbuilder:
|
|
382
|
+
```ruby
|
|
383
|
+
render json: @article.as_json(only: [:id, :title], include: :author)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Medium Projects
|
|
387
|
+
Use Blueprinter for simplicity and speed:
|
|
388
|
+
```ruby
|
|
389
|
+
ArticleBlueprint.render(@article, view: :detailed)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Large Projects / JSON:API Clients
|
|
393
|
+
Use JSONAPI::Serializer for spec compliance:
|
|
394
|
+
```ruby
|
|
395
|
+
ArticleSerializer.new(@article, include: [:author]).serializable_hash
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Performance Tips
|
|
399
|
+
|
|
400
|
+
1. **Select Only Needed Fields**
|
|
401
|
+
```ruby
|
|
402
|
+
# Bad - loads all columns
|
|
403
|
+
@articles = Article.all
|
|
404
|
+
|
|
405
|
+
# Good - only loads needed columns
|
|
406
|
+
@articles = Article.select(:id, :title, :body, :created_at)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
2. **Eager Load Associations**
|
|
410
|
+
```ruby
|
|
411
|
+
# Bad - N+1 queries
|
|
412
|
+
@articles = Article.all
|
|
413
|
+
|
|
414
|
+
# Good - single query
|
|
415
|
+
@articles = Article.includes(:author, :comments)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
3. **Use Caching**
|
|
419
|
+
```ruby
|
|
420
|
+
# In serializer/blueprint
|
|
421
|
+
def show
|
|
422
|
+
json = Rails.cache.fetch([@article, 'v1']) do
|
|
423
|
+
ArticleBlueprint.render(@article)
|
|
424
|
+
end
|
|
425
|
+
render json: json
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
4. **Paginate Collections**
|
|
430
|
+
```ruby
|
|
431
|
+
@articles = Article.page(params[:page]).per(20)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Testing Serializers
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
# spec/blueprints/article_blueprint_spec.rb
|
|
438
|
+
RSpec.describe ArticleBlueprint do
|
|
439
|
+
let(:article) { create(:article, title: 'Test') }
|
|
440
|
+
|
|
441
|
+
it 'serializes basic fields' do
|
|
442
|
+
result = JSON.parse(ArticleBlueprint.render(article))
|
|
443
|
+
|
|
444
|
+
expect(result['id']).to eq(article.id)
|
|
445
|
+
expect(result['title']).to eq('Test')
|
|
446
|
+
expect(result).to have_key('created_at')
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
it 'includes author in extended view' do
|
|
450
|
+
result = JSON.parse(ArticleBlueprint.render(article, view: :extended))
|
|
451
|
+
|
|
452
|
+
expect(result).to have_key('author')
|
|
453
|
+
expect(result['author']['name']).to eq(article.author.name)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-auth-with-devise
|
|
3
|
+
description: "Complete authentication setup for Ruby on Rails applications using Devise. Use when: (1) Setting up user authentication in a Rails app, (2) Adding sign in/sign up/sign out functionality, (3) Implementing email confirmation, password recovery, or account locking, (4) Configuring OmniAuth social login, (5) Adding multiple user models (User/Admin), (6) Customizing Devise views or controllers, (7) Testing authentication with RSpec/Minitest, (8) API authentication setup"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rails Authentication with Devise
|
|
7
|
+
|
|
8
|
+
Devise is the most popular authentication solution for Rails, providing a complete MVC solution with 10 modular components.
|
|
9
|
+
|
|
10
|
+
## Quick Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Add to Gemfile
|
|
14
|
+
bundle add devise
|
|
15
|
+
|
|
16
|
+
# Install Devise
|
|
17
|
+
rails generate devise:install
|
|
18
|
+
|
|
19
|
+
# Generate User model with authentication
|
|
20
|
+
rails generate devise User
|
|
21
|
+
|
|
22
|
+
# Run migrations
|
|
23
|
+
rails db:migrate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Essential Configuration
|
|
27
|
+
|
|
28
|
+
After `devise:install`, configure in `config/environments/development.rb`:
|
|
29
|
+
```ruby
|
|
30
|
+
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Set root route in `config/routes.rb`:
|
|
34
|
+
```ruby
|
|
35
|
+
root to: 'home#index'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Devise Modules Reference
|
|
39
|
+
|
|
40
|
+
Enable modules in the model (e.g., `app/models/user.rb`):
|
|
41
|
+
|
|
42
|
+
| Module | Purpose | Migration Columns |
|
|
43
|
+
|--------|---------|-------------------|
|
|
44
|
+
| `:database_authenticatable` | Password hashing/storage | `email`, `encrypted_password` |
|
|
45
|
+
| `:registerable` | Sign up, edit, destroy account | - |
|
|
46
|
+
| `:recoverable` | Password reset via email | `reset_password_token`, `reset_password_sent_at` |
|
|
47
|
+
| `:rememberable` | "Remember me" cookie | `remember_created_at` |
|
|
48
|
+
| `:trackable` | Sign in stats | `sign_in_count`, `current_sign_in_at`, `last_sign_in_at`, `current_sign_in_ip`, `last_sign_in_ip` |
|
|
49
|
+
| `:validatable` | Email/password validations | - |
|
|
50
|
+
| `:confirmable` | Email confirmation | `confirmation_token`, `confirmed_at`, `confirmation_sent_at`, `unconfirmed_email` |
|
|
51
|
+
| `:lockable` | Lock after failed attempts | `failed_attempts`, `unlock_token`, `locked_at` |
|
|
52
|
+
| `:timeoutable` | Session expiration | - |
|
|
53
|
+
| `:omniauthable` | OAuth provider support | - |
|
|
54
|
+
|
|
55
|
+
## Controller Helpers
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Require authentication
|
|
59
|
+
before_action :authenticate_user!
|
|
60
|
+
|
|
61
|
+
# Check if signed in
|
|
62
|
+
user_signed_in?
|
|
63
|
+
|
|
64
|
+
# Get current user
|
|
65
|
+
current_user
|
|
66
|
+
|
|
67
|
+
# Access session
|
|
68
|
+
user_session
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For other models (e.g., Admin):
|
|
72
|
+
```ruby
|
|
73
|
+
before_action :authenticate_admin!
|
|
74
|
+
admin_signed_in?
|
|
75
|
+
current_admin
|
|
76
|
+
admin_session
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Common Tasks
|
|
80
|
+
|
|
81
|
+
### Add Custom Fields (e.g., username)
|
|
82
|
+
|
|
83
|
+
1. Generate migration:
|
|
84
|
+
```bash
|
|
85
|
+
rails g migration AddUsernameToUsers username:string:uniq
|
|
86
|
+
rails db:migrate
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
2. Permit in `ApplicationController`:
|
|
90
|
+
```ruby
|
|
91
|
+
class ApplicationController < ActionController::Base
|
|
92
|
+
before_action :configure_permitted_parameters, if: :devise_controller?
|
|
93
|
+
|
|
94
|
+
protected
|
|
95
|
+
|
|
96
|
+
def configure_permitted_parameters
|
|
97
|
+
devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
|
|
98
|
+
devise_parameter_sanitizer.permit(:account_update, keys: [:username])
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Customize Views
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Generate all views
|
|
107
|
+
rails generate devise:views
|
|
108
|
+
|
|
109
|
+
# Scoped views for specific model
|
|
110
|
+
rails generate devise:views users
|
|
111
|
+
|
|
112
|
+
# Specific modules only
|
|
113
|
+
rails generate devise:views -v registrations confirmations
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Customize Controllers
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Generate controllers
|
|
120
|
+
rails generate devise:controllers users
|
|
121
|
+
|
|
122
|
+
# Or specific controller
|
|
123
|
+
rails generate devise:controllers users -c sessions registrations
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Update routes:
|
|
127
|
+
```ruby
|
|
128
|
+
devise_for :users, controllers: {
|
|
129
|
+
sessions: 'users/sessions',
|
|
130
|
+
registrations: 'users/registrations'
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Custom Redirect After Sign In
|
|
135
|
+
|
|
136
|
+
In `ApplicationController`:
|
|
137
|
+
```ruby
|
|
138
|
+
def after_sign_in_path_for(resource)
|
|
139
|
+
stored_location_for(resource) || dashboard_path
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def after_sign_out_path_for(resource_or_scope)
|
|
143
|
+
root_path
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Hotwire/Turbo Configuration (Rails 7+)
|
|
148
|
+
|
|
149
|
+
In `config/initializers/devise.rb`:
|
|
150
|
+
```ruby
|
|
151
|
+
Devise.setup do |config|
|
|
152
|
+
config.responder.error_status = :unprocessable_entity
|
|
153
|
+
config.responder.redirect_status = :see_other
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Ensure `responders` gem version >= 3.1.0.
|
|
158
|
+
|
|
159
|
+
## Testing
|
|
160
|
+
|
|
161
|
+
### RSpec Setup
|
|
162
|
+
|
|
163
|
+
In `spec/support/devise.rb`:
|
|
164
|
+
```ruby
|
|
165
|
+
RSpec.configure do |config|
|
|
166
|
+
config.include Devise::Test::ControllerHelpers, type: :controller
|
|
167
|
+
config.include Devise::Test::ControllerHelpers, type: :view
|
|
168
|
+
config.include Devise::Test::IntegrationHelpers, type: :feature
|
|
169
|
+
config.include Devise::Test::IntegrationHelpers, type: :request
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Usage:
|
|
174
|
+
```ruby
|
|
175
|
+
sign_in user
|
|
176
|
+
sign_out user
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Minitest Setup
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
class ActionDispatch::IntegrationTest
|
|
183
|
+
include Devise::Test::IntegrationHelpers
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Additional Guides
|
|
188
|
+
|
|
189
|
+
- **OmniAuth setup**: See [references/omniauth.md](references/omniauth.md)
|
|
190
|
+
- **API authentication**: See [references/api-auth.md](references/api-auth.md)
|
|
191
|
+
- **Advanced patterns**: See [references/advanced.md](references/advanced.md)
|