red64-cli 0.1.0 → 0.3.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 +1 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/ListScreen.d.ts.map +1 -1
- package/dist/components/screens/ListScreen.js +28 -3
- package/dist/components/screens/ListScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +212 -13
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
- package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
- package/dist/components/ui/ArtifactsSidebar.js +51 -0
- package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
- package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
- package/dist/components/ui/FeatureSidebar.js +1 -1
- package/dist/components/ui/FeatureSidebar.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +3 -3
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# Rails 8 Best Practices and Anti-Patterns
|
|
2
|
+
|
|
3
|
+
Project memory for Rails 8 development patterns, common pitfalls, and modern conventions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Rails 8 Specific Features
|
|
8
|
+
|
|
9
|
+
### Solid Stack (Database-Backed Infrastructure)
|
|
10
|
+
|
|
11
|
+
Rails 8 replaces Redis dependencies with database-backed adapters:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# config/application.rb - Production configuration
|
|
15
|
+
config.active_job.queue_adapter = :solid_queue
|
|
16
|
+
config.cache_store = :solid_cache_store
|
|
17
|
+
config.action_cable.adapter = :solid_cable
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Best Practice**: Use Solid Stack for simpler operations (no Redis dependency).
|
|
21
|
+
|
|
22
|
+
**Anti-Pattern**: Adding Redis for simple queue/cache needs when Solid Stack suffices.
|
|
23
|
+
|
|
24
|
+
### Modern Browser Enforcement
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# ApplicationController - Rails 8.1 default
|
|
28
|
+
allow_browser versions: :modern
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Best Practice**: Keep this default; it gracefully handles legacy browser warnings.
|
|
32
|
+
|
|
33
|
+
**Anti-Pattern**: Removing this and manually checking browser capabilities.
|
|
34
|
+
|
|
35
|
+
### Importmap-Based JavaScript
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# config/importmap.rb - ESM imports without bundler
|
|
39
|
+
pin "application"
|
|
40
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
|
41
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Best Practice**: Use importmaps for application JS; avoid bundlers unless necessary.
|
|
45
|
+
|
|
46
|
+
**Anti-Pattern**: Adding Webpack/esbuild for simple JS needs that importmaps handle.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Hotwire Best Practices
|
|
51
|
+
|
|
52
|
+
### Turbo Frames
|
|
53
|
+
|
|
54
|
+
Use Turbo Frames for partial page updates without custom JavaScript:
|
|
55
|
+
|
|
56
|
+
```erb
|
|
57
|
+
<%# Best Practice: Scoped updates %>
|
|
58
|
+
<%= turbo_frame_tag "sources" do %>
|
|
59
|
+
<%= render @sources %>
|
|
60
|
+
<% end %>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Anti-Pattern**: Using JavaScript fetch() for simple HTML replacements.
|
|
64
|
+
|
|
65
|
+
### Turbo Streams
|
|
66
|
+
|
|
67
|
+
Use for real-time broadcasts from models:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Model callback pattern (as seen in Source model)
|
|
71
|
+
class Source < ApplicationRecord
|
|
72
|
+
include Turbo::Broadcastable
|
|
73
|
+
|
|
74
|
+
after_update_commit :broadcast_status_change, if: :saved_change_to_status?
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def broadcast_status_change
|
|
79
|
+
broadcast_replace_later_to(
|
|
80
|
+
project,
|
|
81
|
+
target: dom_target,
|
|
82
|
+
partial: "sources/source",
|
|
83
|
+
locals: { source: self }
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Best Practice**: Use `broadcast_replace_later_to` for async broadcasts from jobs.
|
|
90
|
+
|
|
91
|
+
**Anti-Pattern**: Synchronous broadcasts in model callbacks (blocks request).
|
|
92
|
+
|
|
93
|
+
### Stimulus Controllers
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// Best Practice: Minimal, focused controllers
|
|
97
|
+
import { Controller } from "@hotwired/stimulus"
|
|
98
|
+
|
|
99
|
+
export default class extends Controller {
|
|
100
|
+
static targets = ["indicator"]
|
|
101
|
+
|
|
102
|
+
connect() {
|
|
103
|
+
// Clean setup with bound handlers
|
|
104
|
+
this.handleLoad = this.handleLoad.bind(this)
|
|
105
|
+
this.element.addEventListener("turbo:frame-load", this.handleLoad)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
disconnect() {
|
|
109
|
+
// Always clean up listeners
|
|
110
|
+
this.element.removeEventListener("turbo:frame-load", this.handleLoad)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Anti-Pattern**: Large Stimulus controllers doing too much (split into multiple).
|
|
116
|
+
|
|
117
|
+
**Anti-Pattern**: Not cleaning up event listeners in `disconnect()`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Controller Patterns
|
|
122
|
+
|
|
123
|
+
### RESTful Design
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
# Best Practice: Standard REST actions with respond_to
|
|
127
|
+
def create
|
|
128
|
+
@source = @project.sources.build(source_params)
|
|
129
|
+
|
|
130
|
+
respond_to do |format|
|
|
131
|
+
if @source.save
|
|
132
|
+
format.html { redirect_to @source, notice: "Created." }
|
|
133
|
+
format.turbo_stream
|
|
134
|
+
else
|
|
135
|
+
format.html { render :new, status: :unprocessable_entity }
|
|
136
|
+
format.turbo_stream { render :form_update, status: :unprocessable_entity }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Anti-Pattern**: Custom action names when REST actions work (avoid `do_create`, `process_form`).
|
|
143
|
+
|
|
144
|
+
### Strong Parameters
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Best Practice: Separate params for create vs update when needed
|
|
148
|
+
def source_params
|
|
149
|
+
params.require(:source).permit(:url, :title, :notes)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def update_params
|
|
153
|
+
# URL is immutable after creation
|
|
154
|
+
params.require(:source).permit(:title, :notes)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Anti-Pattern**: Permitting `.permit!` or overly broad parameters.
|
|
159
|
+
|
|
160
|
+
### Scoped Queries Through Current User
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# Best Practice: Always scope through authenticated user
|
|
164
|
+
def set_source
|
|
165
|
+
@source = Source.joins(:project)
|
|
166
|
+
.where(projects: { user_id: Current.user.id })
|
|
167
|
+
.find(params[:id])
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Anti-Pattern**: `Source.find(params[:id])` without user scoping (authorization bypass).
|
|
172
|
+
|
|
173
|
+
### Global Exception Handling
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Best Practice: Centralized error handling with format awareness
|
|
177
|
+
class ApplicationController < ActionController::Base
|
|
178
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
|
179
|
+
respond_to do |format|
|
|
180
|
+
format.html { render file: Rails.public_path.join("404.html"), status: :not_found }
|
|
181
|
+
format.turbo_stream { head :not_found }
|
|
182
|
+
format.json { render json: { error: "Not found" }, status: :not_found }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Anti-Pattern**: Handling exceptions in every controller action.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Model Patterns
|
|
193
|
+
|
|
194
|
+
### Organization Structure
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
class Source < ApplicationRecord
|
|
198
|
+
# 1. Includes
|
|
199
|
+
include Turbo::Broadcastable
|
|
200
|
+
|
|
201
|
+
# 2. Associations
|
|
202
|
+
belongs_to :project
|
|
203
|
+
has_many :ideas, through: :idea_sources
|
|
204
|
+
|
|
205
|
+
# 3. Enums
|
|
206
|
+
enum :status, { pending: 0, collecting: 1, collected: 2, failed: 3 }
|
|
207
|
+
|
|
208
|
+
# 4. Validations
|
|
209
|
+
validates :url, presence: true
|
|
210
|
+
|
|
211
|
+
# 5. Scopes
|
|
212
|
+
scope :pending_processing, -> { where(processing_status: :pending) }
|
|
213
|
+
|
|
214
|
+
# 6. Callbacks (use sparingly)
|
|
215
|
+
after_create_commit :enqueue_processing, if: :should_process?
|
|
216
|
+
|
|
217
|
+
# 7. Public instance methods
|
|
218
|
+
def needs_processing?
|
|
219
|
+
processing_pending? || processing_failed?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
private
|
|
223
|
+
|
|
224
|
+
# 8. Private methods
|
|
225
|
+
def enqueue_processing
|
|
226
|
+
ProcessingJob.perform_later(id)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Enum Best Practices
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# Best Practice: Use symbol keys, add prefix for conflicts
|
|
235
|
+
enum :status, { pending: 0, failed: 3 }
|
|
236
|
+
enum :processing_status, { pending: "pending", failed: "failed" }, prefix: :processing
|
|
237
|
+
|
|
238
|
+
# Usage: source.pending? vs source.processing_pending?
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Anti-Pattern**: String-based enums without prefix when names conflict.
|
|
242
|
+
|
|
243
|
+
### Scopes Over Query Methods
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
# Best Practice: Chainable scopes
|
|
247
|
+
scope :from_feed, ->(feed) { where(feed: feed) }
|
|
248
|
+
scope :with_full_metadata, -> { where.not(title: [nil, ""]).where.not(description: [nil, ""]) }
|
|
249
|
+
|
|
250
|
+
# Usage: Source.from_feed(feed).with_full_metadata.collected
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Anti-Pattern**: Class methods that return single records or non-chainable results.
|
|
254
|
+
|
|
255
|
+
### Callback Discipline
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
# Best Practice: Callbacks for model-internal concerns only
|
|
259
|
+
after_create_commit :enqueue_job, if: :should_enqueue? # Acceptable
|
|
260
|
+
before_validation :normalize_url, on: :create # Acceptable
|
|
261
|
+
|
|
262
|
+
# Anti-Pattern: Business logic in callbacks
|
|
263
|
+
after_save :send_notification_email # Move to service/controller
|
|
264
|
+
after_save :update_related_records # Use transactions in service
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Background Job Patterns
|
|
270
|
+
|
|
271
|
+
### Job Structure
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
class CollectContentJob < ApplicationJob
|
|
275
|
+
queue_as :default
|
|
276
|
+
|
|
277
|
+
# Retry transient errors with backoff
|
|
278
|
+
retry_on ApiClient::ApiError,
|
|
279
|
+
wait: :polynomially_longer,
|
|
280
|
+
attempts: 3 do |job, error|
|
|
281
|
+
job.send(:mark_failed, error)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Discard non-retryable errors
|
|
285
|
+
discard_on ApiClient::ConfigurationError do |job, error|
|
|
286
|
+
job.send(:mark_failed, error)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
discard_on ActiveRecord::RecordNotFound
|
|
290
|
+
|
|
291
|
+
def perform(source_id)
|
|
292
|
+
source = Source.find_by(id: source_id)
|
|
293
|
+
return if source.nil? # Deleted while queued
|
|
294
|
+
|
|
295
|
+
# Job logic here
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private
|
|
299
|
+
|
|
300
|
+
def mark_failed(error)
|
|
301
|
+
# Handle failure state
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Best Practice**: Use `find_by` + nil check for records that might be deleted.
|
|
307
|
+
|
|
308
|
+
**Anti-Pattern**: Using `find` without handling `RecordNotFound` in jobs.
|
|
309
|
+
|
|
310
|
+
### Rate Limit Handling
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
# Best Practice: Extract wait time from rate limit errors
|
|
314
|
+
retry_on ApiClient::RateLimitError,
|
|
315
|
+
wait: ->(executions, exception) { exception.retry_after || (executions**4) + 2 },
|
|
316
|
+
attempts: 3
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Service Object Patterns
|
|
322
|
+
|
|
323
|
+
### Result Objects
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
# Best Practice: Explicit result types
|
|
327
|
+
class ContentCollectionService
|
|
328
|
+
class Result
|
|
329
|
+
attr_reader :job_id, :error_type, :error_message
|
|
330
|
+
|
|
331
|
+
def success? = @success
|
|
332
|
+
def error? = !@success
|
|
333
|
+
|
|
334
|
+
def self.success(job_id:)
|
|
335
|
+
new(success: true, job_id: job_id)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def self.error(type:, message:)
|
|
339
|
+
new(success: false, error_type: type, error_message: message)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Anti-Pattern**: Returning `true`/`false` or raising exceptions for expected failures.
|
|
346
|
+
|
|
347
|
+
### Error Hierarchy
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
# Best Practice: Domain-specific error classes
|
|
351
|
+
class LlmService
|
|
352
|
+
class Error < StandardError; end
|
|
353
|
+
class ConfigurationError < Error; end
|
|
354
|
+
class AuthenticationError < Error; end
|
|
355
|
+
class RateLimitError < Error; end
|
|
356
|
+
|
|
357
|
+
def call
|
|
358
|
+
# Map external errors to domain errors
|
|
359
|
+
rescue ExternalLib::UnauthorizedError => e
|
|
360
|
+
raise AuthenticationError, e.message
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Security Practices
|
|
368
|
+
|
|
369
|
+
### Current User Pattern
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
# Best Practice: Thread-safe current user via Current
|
|
373
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
374
|
+
attribute :user, :session
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Set in controller
|
|
378
|
+
Current.user = user_from_session
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Credential Sanitization
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
# Best Practice: Sanitize error messages
|
|
385
|
+
def log_error_safely(message)
|
|
386
|
+
sanitized = message.gsub(/[a-zA-Z0-9_-]{20,}/, "[FILTERED]")
|
|
387
|
+
Rails.logger.error(sanitized)
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Anti-Pattern**: Logging raw API responses that may contain keys.
|
|
392
|
+
|
|
393
|
+
### Input Validation
|
|
394
|
+
|
|
395
|
+
```ruby
|
|
396
|
+
# Best Practice: Validate URLs explicitly
|
|
397
|
+
validates :url, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]) }
|
|
398
|
+
validates :image_url, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]), allow_blank: true }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Performance Anti-Patterns
|
|
404
|
+
|
|
405
|
+
### N+1 Queries
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
# Anti-Pattern
|
|
409
|
+
@sources.each { |s| s.project.name }
|
|
410
|
+
|
|
411
|
+
# Best Practice
|
|
412
|
+
@sources = Source.includes(:project)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Unbounded Queries
|
|
416
|
+
|
|
417
|
+
```ruby
|
|
418
|
+
# Anti-Pattern
|
|
419
|
+
Source.all.each { |s| process(s) }
|
|
420
|
+
|
|
421
|
+
# Best Practice
|
|
422
|
+
Source.find_each(batch_size: 100) { |s| process(s) }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Missing Indexes
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
# Best Practice: Index foreign keys and common query columns
|
|
429
|
+
add_index :sources, [:project_id, :status]
|
|
430
|
+
add_index :sources, [:feed_id, :created_at]
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Testing Anti-Patterns
|
|
436
|
+
|
|
437
|
+
### Testing Implementation Instead of Behavior
|
|
438
|
+
|
|
439
|
+
```ruby
|
|
440
|
+
# Anti-Pattern
|
|
441
|
+
test "calls the right method" do
|
|
442
|
+
expect(service).to receive(:internal_method)
|
|
443
|
+
service.call
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Best Practice
|
|
447
|
+
test "returns success when valid" do
|
|
448
|
+
result = service.call
|
|
449
|
+
assert result.success?
|
|
450
|
+
end
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Missing HTTP Stubs
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
# Best Practice: Stub external requests in setup
|
|
457
|
+
setup do
|
|
458
|
+
stub_request(:head, /example\.com/).to_return(status: 200)
|
|
459
|
+
end
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Common Pitfalls
|
|
465
|
+
|
|
466
|
+
| Pitfall | Solution |
|
|
467
|
+
|---------|----------|
|
|
468
|
+
| Forgetting `status: :unprocessable_entity` on render | Always include for form errors |
|
|
469
|
+
| Using `find` in jobs for deletable records | Use `find_by` with nil check |
|
|
470
|
+
| Inline Turbo Stream logic | Create `.turbo_stream.erb` views |
|
|
471
|
+
| Synchronous broadcasts in callbacks | Use `broadcast_*_later_to` |
|
|
472
|
+
| Raw SQL with user input | Use parameterized queries or scopes |
|
|
473
|
+
| Large service objects | Split by responsibility |
|
|
474
|
+
| Callbacks for business logic | Move to services |
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Quick Reference
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
# Development commands
|
|
482
|
+
bin/rails server
|
|
483
|
+
bin/rails console
|
|
484
|
+
bin/rails test
|
|
485
|
+
bin/rails test:system
|
|
486
|
+
|
|
487
|
+
# Code quality
|
|
488
|
+
bundle exec rubocop
|
|
489
|
+
bundle exec brakeman
|
|
490
|
+
|
|
491
|
+
# Database
|
|
492
|
+
bin/rails db:migrate
|
|
493
|
+
bin/rails db:seed
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
_Focus on Rails 8 conventions and modern patterns. See `rails.md` for additional conventions._
|