agent-notes 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_notes/VERSION +1 -0
- agent_notes/__init__.py +1 -0
- agent_notes/__main__.py +4 -0
- agent_notes/cli.py +348 -0
- agent_notes/commands/__init__.py +27 -0
- agent_notes/commands/_install_helpers.py +262 -0
- agent_notes/commands/build.py +170 -0
- agent_notes/commands/doctor.py +112 -0
- agent_notes/commands/info.py +95 -0
- agent_notes/commands/install.py +99 -0
- agent_notes/commands/list.py +169 -0
- agent_notes/commands/memory.py +430 -0
- agent_notes/commands/regenerate.py +152 -0
- agent_notes/commands/set_role.py +143 -0
- agent_notes/commands/uninstall.py +26 -0
- agent_notes/commands/update.py +169 -0
- agent_notes/commands/validate.py +199 -0
- agent_notes/commands/wizard.py +720 -0
- agent_notes/config.py +154 -0
- agent_notes/data/agents/agents.yaml +352 -0
- agent_notes/data/agents/analyst.md +45 -0
- agent_notes/data/agents/api-reviewer.md +47 -0
- agent_notes/data/agents/architect.md +46 -0
- agent_notes/data/agents/coder.md +28 -0
- agent_notes/data/agents/database-specialist.md +45 -0
- agent_notes/data/agents/debugger.md +47 -0
- agent_notes/data/agents/devil.md +47 -0
- agent_notes/data/agents/devops.md +38 -0
- agent_notes/data/agents/explorer.md +23 -0
- agent_notes/data/agents/integrations.md +44 -0
- agent_notes/data/agents/lead.md +216 -0
- agent_notes/data/agents/performance-profiler.md +44 -0
- agent_notes/data/agents/refactorer.md +48 -0
- agent_notes/data/agents/reviewer.md +44 -0
- agent_notes/data/agents/security-auditor.md +44 -0
- agent_notes/data/agents/system-auditor.md +38 -0
- agent_notes/data/agents/tech-writer.md +32 -0
- agent_notes/data/agents/test-runner.md +36 -0
- agent_notes/data/agents/test-writer.md +39 -0
- agent_notes/data/cli/claude.yaml +25 -0
- agent_notes/data/cli/copilot.yaml +18 -0
- agent_notes/data/cli/opencode.yaml +22 -0
- agent_notes/data/commands/brainstorm.md +8 -0
- agent_notes/data/commands/debug.md +9 -0
- agent_notes/data/commands/review.md +10 -0
- agent_notes/data/global-claude.md +290 -0
- agent_notes/data/global-copilot.md +27 -0
- agent_notes/data/global-opencode.md +40 -0
- agent_notes/data/hooks/session-context.md.tpl +19 -0
- agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
- agent_notes/data/models/claude-opus-4-1.yaml +16 -0
- agent_notes/data/models/claude-opus-4-5.yaml +16 -0
- agent_notes/data/models/claude-opus-4-6.yaml +16 -0
- agent_notes/data/models/claude-opus-4-7.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
- agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4.yaml +16 -0
- agent_notes/data/pricing.yaml +33 -0
- agent_notes/data/roles/orchestrator.yaml +5 -0
- agent_notes/data/roles/reasoner.yaml +5 -0
- agent_notes/data/roles/scout.yaml +5 -0
- agent_notes/data/roles/worker.yaml +5 -0
- agent_notes/data/rules/code-quality.md +9 -0
- agent_notes/data/rules/safety.md +10 -0
- agent_notes/data/scripts/cost-report +211 -0
- agent_notes/data/skills/brainstorming/SKILL.md +57 -0
- agent_notes/data/skills/code-review/SKILL.md +64 -0
- agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
- agent_notes/data/skills/docker-compose/SKILL.md +318 -0
- agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
- agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
- agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
- agent_notes/data/skills/git/SKILL.md +87 -0
- agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
- agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
- agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
- agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
- agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
- agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
- agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
- agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
- agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
- agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
- agent_notes/data/skills/rails-lib/SKILL.md +101 -0
- agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
- agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
- agent_notes/data/skills/rails-models/SKILL.md +459 -0
- agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
- agent_notes/data/skills/rails-routes/SKILL.md +804 -0
- agent_notes/data/skills/rails-style/SKILL.md +538 -0
- agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
- agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
- agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
- agent_notes/data/skills/rails-validations/SKILL.md +108 -0
- agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
- agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
- agent_notes/data/skills/rails-views/SKILL.md +413 -0
- agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
- agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
- agent_notes/data/skills/tdd/SKILL.md +57 -0
- agent_notes/data/templates/__init__.py +1 -0
- agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__init__.py +1 -0
- agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/claude.py +44 -0
- agent_notes/data/templates/frontmatter/opencode.py +104 -0
- agent_notes/doctor_checks.py +189 -0
- agent_notes/domain/__init__.py +17 -0
- agent_notes/domain/agent.py +34 -0
- agent_notes/domain/cli_backend.py +40 -0
- agent_notes/domain/diagnostics.py +29 -0
- agent_notes/domain/diff.py +44 -0
- agent_notes/domain/model.py +27 -0
- agent_notes/domain/role.py +13 -0
- agent_notes/domain/rule.py +13 -0
- agent_notes/domain/skill.py +15 -0
- agent_notes/domain/state.py +46 -0
- agent_notes/install_state.py +11 -0
- agent_notes/registries/__init__.py +16 -0
- agent_notes/registries/_base.py +46 -0
- agent_notes/registries/agent_registry.py +107 -0
- agent_notes/registries/cli_registry.py +89 -0
- agent_notes/registries/model_registry.py +85 -0
- agent_notes/registries/role_registry.py +64 -0
- agent_notes/registries/rule_registry.py +80 -0
- agent_notes/registries/skill_registry.py +141 -0
- agent_notes/services/__init__.py +8 -0
- agent_notes/services/diagnostics/__init__.py +47 -0
- agent_notes/services/diagnostics/_checks.py +272 -0
- agent_notes/services/diagnostics/_display.py +346 -0
- agent_notes/services/diagnostics/_fix.py +169 -0
- agent_notes/services/diff.py +349 -0
- agent_notes/services/fs.py +195 -0
- agent_notes/services/install_state_builder.py +210 -0
- agent_notes/services/installer.py +293 -0
- agent_notes/services/memory_backend.py +155 -0
- agent_notes/services/rendering.py +329 -0
- agent_notes/services/session_context.py +23 -0
- agent_notes/services/settings_writer.py +79 -0
- agent_notes/services/state_store.py +249 -0
- agent_notes/services/ui.py +419 -0
- agent_notes/services/user_config.py +62 -0
- agent_notes/services/validation.py +67 -0
- agent_notes/state.py +21 -0
- agent_notes-2.0.4.dist-info/METADATA +14 -0
- agent_notes-2.0.4.dist-info/RECORD +162 -0
- agent_notes-2.0.4.dist-info/WHEEL +5 -0
- agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
- agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
- agent_notes-2.0.4.dist-info/top_level.txt +2 -0
- tests/conftest.py +20 -0
- tests/functional/__init__.py +0 -0
- tests/functional/test_build_commands.py +88 -0
- tests/functional/test_registries.py +128 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_build_output.py +129 -0
- tests/plugins/__init__.py +0 -0
- tests/plugins/test_agents.py +93 -0
- tests/plugins/test_skills.py +77 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-style
|
|
3
|
+
description: "Rails code style: conditionals, method ordering, bang methods, visibility, and naming conventions"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Style Guide
|
|
8
|
+
|
|
9
|
+
Comprehensive guide for Rails code style and conventions.
|
|
10
|
+
|
|
11
|
+
> **Note**: This guide represents opinionated coding style based on Basecamp/37signals practices. While strongly aligned with Rails philosophy and conventions, some preferences (such as favoring expanded conditionals over guard clauses, and the `_later`/`_now` naming pattern) differ from broader Rails community norms. These are battle-tested patterns from production codebases, not universal standards. Adapt these guidelines to match your team's preferences and project needs.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Philosophy
|
|
16
|
+
|
|
17
|
+
Write code that is a pleasure to read. Code style matters because:
|
|
18
|
+
- **Readability** - Code is read more often than written
|
|
19
|
+
- **Consistency** - Predictable patterns reduce cognitive load
|
|
20
|
+
- **Maintainability** - Clear code is easier to modify
|
|
21
|
+
- **Collaboration** - Shared conventions improve team velocity
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Conditional Returns
|
|
26
|
+
|
|
27
|
+
### Prefer Expanded Conditionals
|
|
28
|
+
|
|
29
|
+
**Prefer expanded if/else over guard clauses:**
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# ❌ Bad - Guard clause
|
|
33
|
+
def todos_for_new_group
|
|
34
|
+
ids = params.require(:todolist)[:todo_ids]
|
|
35
|
+
return [] unless ids
|
|
36
|
+
@bucket.recordings.todos.find(ids.split(","))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# ✅ Good - Expanded conditional
|
|
40
|
+
def todos_for_new_group
|
|
41
|
+
if ids = params.require(:todolist)[:todo_ids]
|
|
42
|
+
@bucket.recordings.todos.find(ids.split(","))
|
|
43
|
+
else
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Why:** Guard clauses can be hard to read, especially when nested.
|
|
50
|
+
|
|
51
|
+
### Exception: Early Returns
|
|
52
|
+
|
|
53
|
+
Guard clauses are acceptable when:
|
|
54
|
+
1. The return is right at the beginning of the method
|
|
55
|
+
2. The main method body is non-trivial (several lines)
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# ✅ Acceptable - Early return for clarity
|
|
59
|
+
def after_recorded_as_commit(recording)
|
|
60
|
+
return if recording.parent.was_created?
|
|
61
|
+
|
|
62
|
+
if recording.was_created?
|
|
63
|
+
broadcast_new_column(recording)
|
|
64
|
+
else
|
|
65
|
+
broadcast_column_change(recording)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Methods Ordering
|
|
73
|
+
|
|
74
|
+
### Class-Level Organization
|
|
75
|
+
|
|
76
|
+
Order methods in classes:
|
|
77
|
+
|
|
78
|
+
1. **Class methods**
|
|
79
|
+
2. **Public methods** (with `initialize` first if present)
|
|
80
|
+
3. **Private methods**
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class Card < ApplicationRecord
|
|
84
|
+
# 1. Class methods
|
|
85
|
+
def self.find_stale
|
|
86
|
+
where(last_active_at: ..1.month.ago)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# 2. Public methods
|
|
90
|
+
def initialize(*args)
|
|
91
|
+
super
|
|
92
|
+
@initialized = true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def close
|
|
96
|
+
create_closure!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def reopen
|
|
100
|
+
closure.destroy
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 3. Private methods
|
|
104
|
+
private
|
|
105
|
+
def validate_state
|
|
106
|
+
# ...
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Invocation Order (Vertical Reading)
|
|
112
|
+
|
|
113
|
+
Order methods based on their invocation order. This helps understand code flow:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
class SomeClass
|
|
117
|
+
def process
|
|
118
|
+
step_one
|
|
119
|
+
step_two
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
# Step one and its sub-methods
|
|
124
|
+
def step_one
|
|
125
|
+
step_one_a
|
|
126
|
+
step_one_b
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def step_one_a
|
|
130
|
+
# Implementation
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def step_one_b
|
|
134
|
+
# Implementation
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Step two and its sub-methods
|
|
138
|
+
def step_two
|
|
139
|
+
step_two_a
|
|
140
|
+
step_two_b
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def step_two_a
|
|
144
|
+
# Implementation
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def step_two_b
|
|
148
|
+
# Implementation
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Why:** Reading top-to-bottom mirrors execution flow.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Bang Methods (!)
|
|
158
|
+
|
|
159
|
+
### Only Use ! When Non-Bang Version Exists
|
|
160
|
+
|
|
161
|
+
**Rule:** Only use `!` for methods that have a corresponding version without `!`.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
# ✅ Good - Has save counterpart
|
|
165
|
+
def save!
|
|
166
|
+
raise unless save
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# ❌ Bad - No close counterpart
|
|
170
|
+
def close!
|
|
171
|
+
create_closure!
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# ✅ Good - Just use close
|
|
175
|
+
def close
|
|
176
|
+
create_closure!
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Why:** We don't use `!` to flag destructive actions. Many destructive methods in Ruby and Rails don't end with `!` (`destroy`, `delete`, `update`, etc.).
|
|
181
|
+
|
|
182
|
+
**Examples of correct usage:**
|
|
183
|
+
- `save` / `save!`
|
|
184
|
+
- `create` / `create!`
|
|
185
|
+
- `update` / `update!`
|
|
186
|
+
- `destroy` / `destroy!`
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Visibility Modifiers
|
|
191
|
+
|
|
192
|
+
### No Newline Under Modifier, Indent Content
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
# ✅ Good
|
|
196
|
+
class SomeClass
|
|
197
|
+
def public_method
|
|
198
|
+
# Implementation
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
def private_method_1
|
|
203
|
+
# Implementation
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def private_method_2
|
|
207
|
+
# Implementation
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
# ❌ Bad - Extra newline, no indentation
|
|
214
|
+
class SomeClass
|
|
215
|
+
def public_method
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def private_method
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Module with Only Private Methods
|
|
226
|
+
|
|
227
|
+
If a module has only private methods, mark `private` at the top with an extra newline but don't indent:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
# ✅ Good - Module with only private methods
|
|
231
|
+
module SomeModule
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
def some_private_method
|
|
235
|
+
# Implementation
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def another_private_method
|
|
239
|
+
# Implementation
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## CRUD Controllers
|
|
247
|
+
|
|
248
|
+
### Model Actions as Resources
|
|
249
|
+
|
|
250
|
+
Model web endpoints as CRUD operations on resources (REST). When an action doesn't map to a standard CRUD verb, introduce a new resource:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
# ❌ Bad - Custom actions
|
|
254
|
+
resources :cards do
|
|
255
|
+
post :close
|
|
256
|
+
post :reopen
|
|
257
|
+
post :gild
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# ✅ Good - Actions as resources
|
|
261
|
+
resources :cards do
|
|
262
|
+
resource :closure # POST = close, DELETE = reopen
|
|
263
|
+
resource :goldness # POST = gild, DELETE = ungild
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Controller and Model Interactions
|
|
270
|
+
|
|
271
|
+
### Vanilla Rails Approach
|
|
272
|
+
|
|
273
|
+
Favor thin controllers directly invoking a rich domain model. Don't use services or other artifacts to connect the two unless necessary.
|
|
274
|
+
|
|
275
|
+
### Simple Operations
|
|
276
|
+
|
|
277
|
+
Invoking plain Active Record operations is totally fine:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# ✅ Good - Direct Active Record
|
|
281
|
+
class Cards::CommentsController < ApplicationController
|
|
282
|
+
def create
|
|
283
|
+
@comment = @card.comments.create!(comment_params)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Complex Operations
|
|
289
|
+
|
|
290
|
+
For complex behavior, prefer clear, intention-revealing model APIs:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
# ✅ Good - Intention-revealing model method
|
|
294
|
+
class Cards::GoldnessesController < ApplicationController
|
|
295
|
+
def create
|
|
296
|
+
@card.gild
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# In model
|
|
301
|
+
class Card < ApplicationRecord
|
|
302
|
+
def gild
|
|
303
|
+
transaction do
|
|
304
|
+
create_goldness!
|
|
305
|
+
track_event :gilded
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Services (When Justified)
|
|
312
|
+
|
|
313
|
+
When justified, it's fine to use services or form objects, but don't treat them as special artifacts:
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
# ✅ Acceptable when complexity warrants it
|
|
317
|
+
class SignupsController < ApplicationController
|
|
318
|
+
def create
|
|
319
|
+
Signup.new(email_address: params[:email]).create_identity
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Async Operations in Jobs
|
|
327
|
+
|
|
328
|
+
### Shallow Jobs That Delegate
|
|
329
|
+
|
|
330
|
+
Write shallow job classes that delegate logic to domain models:
|
|
331
|
+
|
|
332
|
+
**Naming convention:**
|
|
333
|
+
- Use `_later` suffix for methods that enqueue a job
|
|
334
|
+
- Use `_now` suffix for the synchronous version
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
# Model concern
|
|
338
|
+
module Event::Relaying
|
|
339
|
+
extend ActiveSupport::Concern
|
|
340
|
+
|
|
341
|
+
included do
|
|
342
|
+
after_create_commit :relay_later
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def relay_later
|
|
346
|
+
Event::RelayJob.perform_later(self)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def relay_now
|
|
350
|
+
webhooks.active.each { |webhook| webhook.trigger(self) }
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Job delegates to model
|
|
355
|
+
class Event::RelayJob < ApplicationJob
|
|
356
|
+
def perform(event)
|
|
357
|
+
event.relay_now
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Why:** Keeps jobs thin and testable. Business logic stays in models.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## General Code Style
|
|
367
|
+
|
|
368
|
+
### Whitespace
|
|
369
|
+
|
|
370
|
+
```ruby
|
|
371
|
+
# ✅ Good - One space around operators
|
|
372
|
+
x = 1 + 2
|
|
373
|
+
hash = { key: value }
|
|
374
|
+
array = [1, 2, 3]
|
|
375
|
+
|
|
376
|
+
# ❌ Bad
|
|
377
|
+
x=1+2
|
|
378
|
+
hash={key:value}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Line Length
|
|
382
|
+
|
|
383
|
+
- Keep lines under 120 characters when possible
|
|
384
|
+
- Break long lines logically
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
# ✅ Good - Broken at logical points
|
|
388
|
+
User.where(active: true)
|
|
389
|
+
.where(verified: true)
|
|
390
|
+
.order(created_at: :desc)
|
|
391
|
+
|
|
392
|
+
# ❌ Bad - One long line
|
|
393
|
+
User.where(active: true).where(verified: true).order(created_at: :desc).limit(10).offset(20)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### String Literals
|
|
397
|
+
|
|
398
|
+
```ruby
|
|
399
|
+
# ✅ Prefer double quotes for strings
|
|
400
|
+
message = "Hello, world!"
|
|
401
|
+
|
|
402
|
+
# ✅ Single quotes for strings that don't need interpolation or escaping
|
|
403
|
+
sql = 'SELECT * FROM users WHERE id = ?'
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Hash Syntax
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
# ✅ Good - New hash syntax for symbol keys
|
|
410
|
+
user = { name: "Alice", email: "alice@example.com" }
|
|
411
|
+
|
|
412
|
+
# ✅ Old syntax when keys are not symbols
|
|
413
|
+
config = { "Content-Type" => "application/json" }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Code Organization Principles
|
|
419
|
+
|
|
420
|
+
### 1. Extract Complex Conditionals
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
# ❌ Bad - Complex inline conditional
|
|
424
|
+
if user.admin? && (user.verified? || user.trusted?) && user.active?
|
|
425
|
+
grant_access
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# ✅ Good - Extracted to method
|
|
429
|
+
def can_access?
|
|
430
|
+
user.admin? && (user.verified? || user.trusted?) && user.active?
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
if can_access?
|
|
434
|
+
grant_access
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# ✅ Even better - Method on user
|
|
438
|
+
if user.can_access?
|
|
439
|
+
grant_access
|
|
440
|
+
end
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 2. Single Responsibility
|
|
444
|
+
|
|
445
|
+
Each method should have one clear purpose:
|
|
446
|
+
|
|
447
|
+
```ruby
|
|
448
|
+
# ❌ Bad - Multiple responsibilities
|
|
449
|
+
def process_user
|
|
450
|
+
user.verify_email
|
|
451
|
+
user.send_welcome_email
|
|
452
|
+
user.subscribe_to_newsletter
|
|
453
|
+
user.create_default_settings
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# ✅ Good - Separate concerns
|
|
457
|
+
def process_user
|
|
458
|
+
verify_user
|
|
459
|
+
welcome_user
|
|
460
|
+
setup_user_account
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
private
|
|
464
|
+
def verify_user
|
|
465
|
+
user.verify_email
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def welcome_user
|
|
469
|
+
user.send_welcome_email
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def setup_user_account
|
|
473
|
+
user.subscribe_to_newsletter
|
|
474
|
+
user.create_default_settings
|
|
475
|
+
end
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 3. Intention-Revealing Names
|
|
479
|
+
|
|
480
|
+
Use clear, descriptive names:
|
|
481
|
+
|
|
482
|
+
```ruby
|
|
483
|
+
# ❌ Bad - Unclear names
|
|
484
|
+
def process
|
|
485
|
+
data = fetch
|
|
486
|
+
x = transform(data)
|
|
487
|
+
save(x)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# ✅ Good - Clear names
|
|
491
|
+
def import_users
|
|
492
|
+
csv_data = fetch_csv_from_api
|
|
493
|
+
user_records = parse_csv_to_users(csv_data)
|
|
494
|
+
save_users_to_database(user_records)
|
|
495
|
+
end
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Best Practices Summary
|
|
501
|
+
|
|
502
|
+
### ✅ DO
|
|
503
|
+
|
|
504
|
+
1. **Expanded conditionals** over guard clauses (except early returns)
|
|
505
|
+
2. **Order methods** by invocation flow
|
|
506
|
+
3. **Indent under visibility modifiers** (no extra newline)
|
|
507
|
+
4. **Only use !** when non-bang version exists
|
|
508
|
+
5. **Model actions as resources** in routes
|
|
509
|
+
6. **Thin controllers** that delegate to models
|
|
510
|
+
7. **Shallow jobs** with `_later` / `_now` pattern
|
|
511
|
+
8. **Intention-revealing names** for methods and variables
|
|
512
|
+
9. **Extract complex conditionals** to methods
|
|
513
|
+
10. **One responsibility per method**
|
|
514
|
+
|
|
515
|
+
### ❌ DON'T
|
|
516
|
+
|
|
517
|
+
1. **Guard clauses everywhere** - Use expanded conditionals
|
|
518
|
+
2. **Random method order** - Follow invocation order
|
|
519
|
+
3. **Extra newlines after private** - Keep it clean
|
|
520
|
+
4. **Bang methods without counterparts** - Just use regular names
|
|
521
|
+
5. **Custom controller actions** - Use resources
|
|
522
|
+
6. **Fat controllers** - Delegate to models
|
|
523
|
+
7. **Business logic in jobs** - Keep jobs thin
|
|
524
|
+
8. **Cryptic variable names** - Be descriptive
|
|
525
|
+
9. **Complex inline conditionals** - Extract to methods
|
|
526
|
+
10. **Multiple responsibilities** - Keep focused
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Summary
|
|
531
|
+
|
|
532
|
+
- **Conditionals**: Expanded over guards (except early returns)
|
|
533
|
+
- **Methods**: Ordered by invocation, clear names
|
|
534
|
+
- **Visibility**: Indent under modifiers, no extra newlines
|
|
535
|
+
- **Bang**: Only when non-bang version exists
|
|
536
|
+
- **Controllers**: Thin, RESTful, resource-oriented
|
|
537
|
+
- **Jobs**: Shallow, delegate to models, use `_later`/`_now`
|
|
538
|
+
- **Organization**: Single responsibility, intention-revealing names
|