Use when refactoring Ruby/Rails code, organizing methods, deciding on guard clauses vs if/else, or following 37signals conventions - these patterns are counter to standard Ruby style guides
/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.
These conventions CONTRADICT standard Ruby style guides. They reflect production 37signals/Basecamp code.
You're writing Ruby/Rails code for 37signals-style projects. Symptoms that trigger this skill:
! to method nameThese patterns violate what most Ruby developers consider "best practice":
| Pattern | 37signals Way | Standard Ruby Way |
|---|---|---|
| Conditionals | Prefer if/else | Prefer guard clauses |
| Private indentation | Indent under private | No indentation (Rubocop) |
| Bang methods | Only with counterpart | Flag "dangerous" actions |
| Method order | Invocation sequence | Alphabetical |
| Controllers | Thin + rich models | Service objects |
If you're about to do any of these, you're violating 37signals style:
| Red Flag | Instead, Do This |
|---|---|
Add guard clauses (return unless, return if) | Use if/else (unless at method start with complex body) |
| Remove indentation from private methods | Indent 2 spaces under private |
Add ! to a method without counterpart | Use plain name (e.g., close not close!) |
| Alphabetize private methods | Order by invocation sequence |
| Create a service object as special artifact | Move logic to model, call from controller |
These violations require explicit approval. Don't deviate without discussion.
| Situation | 37signals Way | See Section |
|---|---|---|
| Early return needed? | if/else (or guard at method start if body complex) | Expanded Conditionals |
| Private methods? | Indent 2 spaces under private | Private Indentation |
| Ordering methods? | Invocation sequence (caller before callee) | Method Ordering |
| New controller action? | Create new resource instead | CRUD Controllers |
| Complex business logic? | Rich model method, thin controller | Controller/Model |
| Destructive method name? | No ! unless counterpart exists | Bang Methods |
| Background job? | Use _later/_now pattern | Background Jobs |
Prefer if/else over guard clauses - opposite of most Ruby advice:
# Good (37signals style)
def todos_for_new_group
if ids = params.require(:todolist)[:todo_ids]
@bucket.recordings.todos.find(ids.split(","))
else
[]
end
end
# Bad
def todos_for_new_group
ids = params.require(:todolist)[:todo_ids]
return [] unless ids
@bucket.recordings.todos.find(ids.split(","))
end
Why: Guard clauses can be hard to read, especially when nested.
Exception - Guard Clauses ARE Allowed When BOTH:
# Allowed - guard at start, complex body below
def after_recorded_as_commit(recording)
return if recording.parent.was_created?
if recording.was_created?
broadcast_new_column(recording)
else
broadcast_column_change(recording)
end
end
Multiple guard clauses - convert to nested if/else:
# Bad - multiple guard clauses
def process_payment(params)
amount = params[:amount]
return { error: "Missing amount" } unless amount
method = params[:method]
return { error: "Missing method" } unless method
valid = validate_payment(amount, method)
return { error: "Invalid" } unless valid
charge(amount, method)
end
# Good - nested if/else
def process_payment(params)
if amount = params[:amount]
if method = params[:method]
if validate_payment(amount, method)
charge(amount, method)
else
{ error: "Invalid" }
end
else
{ error: "Missing method" }
end
else
{ error: "Missing amount" }
end
end
Indent methods under private - counter to Rubocop default:
class SomeClass
def some_method
# ...
end
private
def private_method_1
# indented 2 spaces
end
def private_method_2
# indented 2 spaces
end
end
Important: No newline after private keyword.
Exception - Module with ONLY Private Methods:
module SomeModule
private
def some_private_method
# not indented
# blank line after private
end
end
Order by invocation sequence, not alphabetically:
class methodspublic methods (with initialize at the very top)private methods (ordered by call sequence)class SomeClass
def self.class_method
# class methods first
end
def initialize
# initialize first among instance methods
end
def some_method
method_1
method_2
end
private
def method_1
method_1_1
method_1_2
end
def method_1_1
# appears after caller (method_1)
end
def method_1_2
# appears after method_1_1
end
def method_2
# appears after method_1 (called second)
end
end
Why: Invocation order shows execution flow. Makes code easier to trace.
Only use ! when non-bang counterpart exists:
# Good - has counterpart
save / save!
update / update!
# Bad - no counterpart, just use `close`
def close!
# wrong - there's no `close` method
end
# Good - single method, no bang
def close
# destructive action, but no bang needed
end
Why: Don't use ! to flag "destructive actions". Many destructive Ruby/Rails methods lack !.
Model actions as CRUD on resources. When action doesn't map to standard CRUD verb, introduce new resource:
# Bad - custom actions
resources :cards do
post :close
post :reopen
end
# Good - new resource
resources :cards do
resource :closure
end
Thin controllers, rich domain models. No service objects as special artifacts.
Plain Active Record is fine:
class Cards::CommentsController < ApplicationController
def create
@comment = @card.comments.create!(comment_params)
end
end
Complex behavior - clear model APIs:
class Cards::GoldnessesController < ApplicationController
def create
@card.gild
end
end
Form objects when truly needed (e.g., coordinating multiple models):
# Signup coordinates Identity + User creation
Signup.new(email_address: email_address).create_identity
Don't create service objects as default pattern. Prefer rich model methods.
Shallow job classes delegating to models:
_later for methods that enqueue jobs_now for synchronous counterpartmodule Event::Relaying
extend ActiveSupport::Concern
included do
after_create_commit :relay_later
end
def relay_later
Event::RelayJob.perform_later(self)
end
def relay_now
# actual logic here
end
end
class Event::RelayJob < ApplicationJob
def perform(event)
event.relay_now
end
end
# WRONG - guard clause after other logic
def process
setup_data
return unless valid?
execute_action
end
# RIGHT - if/else shows full flow
def process
setup_data
if valid?
execute_action
end
end
# WRONG - no indentation under private
class Processor
def process
# ...
end
private
def helper
# ...
end
end
# RIGHT - indent under private
class Processor
def process
# ...
end
private
def helper
# ...
end
end
# WRONG - no close method exists
class Account
def close!
update(closed: true)
end
end
# RIGHT - no bang needed
class Account
def close
update(closed: true)
end
end
# WRONG - alphabetized
class Builder
def build
prepare
format
output
end
private
def format
# ...
end
def output
# ...
end
def prepare
# ...
end
end
# RIGHT - invocation order
class Builder
def build
prepare
format
output
end
private
def prepare
# ...
end
def format
# ...
end
def output
# ...
end
end
| You'll Think | Reality |
|---|---|
| "Guard clauses are Ruby best practice" | 37signals prefers if/else for readability, especially with nesting |
| "Early returns reduce nesting" | Nested if/else shows complete logic flow |
| "Rubocop doesn't indent private methods" | 37signals style intentionally differs from Rubocop |
| "Bang means dangerous/destructive" | Only use ! when you have both safe and dangerous variants |
| "Alphabetical order helps find methods" | Invocation order helps trace execution flow |
| "I should extract a service object" | Keep logic in models, controllers call rich model APIs |
| "Service objects separate concerns" | Only use when truly justified, not as default pattern |
37signals optimizes for reading code, not writing it. These conventions:
These rules apply to ALL new code. Apply these patterns strictly. Don't deviate without explicit approval.
private keyword! to methods with non-bang counterpartsMaster 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.