Use when naming classes, methods, routes in vanilla Rails codebases - fixes concern verb/noun confusion, bang method misuse, custom action anti-patterns
/plugin marketplace add ZempTime/zemptime-marketplace/plugin install vanilla-rails@zemptime-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
37signals naming patterns from production Basecamp codebases (Fizzy, HEY, Basecamp).
Scope: These are 37signals-specific conventions. They differ from generic Rails guides and may contradict advice from other sources. When in doubt, follow these patterns for vanilla Rails codebases.
State changes (pin, close, assign) become resources, not actions:
# ❌ WRONG - Custom actions
resources :cards do
member do
post :pin
post :close
end
end
# ✓ RIGHT - Singular resources
resources :cards do
resource :pin # POST=create, DELETE=destroy
resource :closure # POST=close, DELETE=reopen
end
This creates the naming cascade:
Closeable (adjective)Closure (noun)ClosuresController (plural of noun)resource :closure (singular)| Type | Pattern | Example | ❌ Wrong |
|---|---|---|---|
| Concern | Adjective (-able/-ible ONLY) | Pinnable, Closeable, Assignable | Pinning, Pinnish, Pinlike, PinManager |
| State Model | Noun | Pin, Closure, Assignment | CardPin, CloseRecord |
| State Method | Plain verb (no !) | card.close, card.pin | card.close!, card.pin! |
| Async Enqueue | *_later | notify_watchers_later | notify_watchers_async |
| Sync Execute | Plain or *_now | notify_watchers or notify_watchers_now | - |
| Controller | Plural noun | PinsController, ClosuresController | PinController (singular) |
| Routes | resource (singular) | resource :closure | post :close |
Rationalization: "It describes the behavior being added"
# ❌ WRONG - Verb/noun/other adjective forms
module Card::Pinning # verb -ing
module Card::PinManager # Manager/Handler/Logic
module Card::Pinnish # wrong adjective form
# ✓ RIGHT - Adjective with -able/-ible ONLY
module Card::Pinnable
def pin_by(user); end
end
If concern name doesn't end in -able/-ible, STOP and rename immediately.
Rationalization: "Rails uses bangs for state changes (save!)"
# ❌ WRONG - Contradicts 37signals style
def pin!
pins.create!(user: Current.user)
end
# ✓ RIGHT - Plain method
def pin_by(user)
pins.find_or_create_by!(user: user)
end
Why no bangs: save! is about error handling (raise vs return false). State methods like close don't need this distinction.
Counter-argument: "But my tech lead says Rails conventions use bangs!" - 37signals patterns differ from generic Rails guides. In production Basecamp code, state methods use plain verbs. Follow the codebase style.
Rationalization: "Member routes for individual card actions"
# ❌ WRONG - Custom actions break REST
resources :cards do
member do
post :pin
delete :unpin
end
end
# ✓ RIGHT - Singular resource
resources :cards do
resource :pin, only: [:create, :destroy]
end
Problem: Not recognizing async enqueue pattern
# ❌ WRONG - Unclear if async
def notify_watchers
NotifyWatchersJob.perform_later(self)
end
# ✓ RIGHT - _later suffix on enqueue method
def notify_watchers_later
NotifyWatchersJob.perform_later(self)
end
def notify_watchers # Called by job, does actual work
# actual notification logic
end
Compound verbs: _later always goes at the end:
def pin_and_notify_later # ✓ RIGHT
def notify_after_pin_later # ✓ RIGHT
def later_notify_pin # ❌ WRONG
For "pinning cards":
# app/models/card/pinnable.rb
module Card::Pinnable
extend ActiveSupport::Concern
included do
has_many :pins, dependent: :destroy
end
def pin_by(user)
pins.find_or_create_by!(user: user)
end
def unpin_by(user)
pins.find_by(user: user)&.destroy
end
end
# app/models/pin.rb
class Pin < ApplicationRecord
belongs_to :card
belongs_to :user
end
# app/controllers/cards/pins_controller.rb
class Cards::PinsController < ApplicationController
def create
@pin = @card.pin_by(Current.user)
end
def destroy
@card.unpin_by(Current.user)
end
end
# config/routes.rb
resources :cards do
resource :pin, only: [:create, :destroy], module: :cards
end
app/models/card/closeable.rb # Card-specific concern
app/models/concerns/eventable.rb # Shared concern
app/models/closure.rb # State record
app/controllers/cards/closures_controller.rb
If you see ANY of these, STOP immediately and rename before proceeding:
-able or -ible
-ing, -er, -ish, -like, Logic, Management, Handler, Service! (pin!, close!, assign!)member do or collection do for state changes_later suffixClosureController instead of ClosuresController)Sunk cost fallacy: "I already implemented it wrong" - Rename now. Cost of renaming < cost of confusion later.
Pattern check: Does the naming cascade flow correctly?
Present tense, lowercase, no type prefixes:
✓ add pin feature to cards
✓ extract closeable concern
✓ fix closure validation
✓ update card pinning logic
✓ remove unused pin methods
✓ rename pinning concern to pinnable
❌ feat: Add pin feature # no conventional commit prefixes
❌ Added pin feature # no past tense
❌ [FEATURE] Pin cards # no brackets/tags
❌ Add Pin Feature # no title case
❌ adding pin feature # no -ing form
Time pressure: "Need to commit fast" - Still follow the format. Takes 2 seconds to fix.
| Excuse | Reality |
|---|---|
| "Rails conventions say to use bangs" | 37signals style differs. Follow the codebase patterns. |
| "Pinnable sounds weird, I'll use Pinnish" | Only -able/-ible adjectives. Rename immediately. |
| "Already implemented as Pinning" | Rename now. Sunk cost < confusion cost. |
| "Team will understand PinManagement" | No. Use Pinnable. Pattern must be consistent. |
| "This is complex, needs a service" | Concerns handle complexity fine. See actual Fizzy code. |
| "Member routes are clearer" | Singular resources are the pattern. Follow it. |
| "Commit message format doesn't matter" | It does. Present tense, lowercase, no prefixes. |
| "Close enough, ship it" | Wrong names compound. Fix now. |
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues.