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 counterpartsBuild robust backtesting systems for trading strategies with proper handling of look-ahead bias, survivorship bias, and transaction costs. Use when developing trading algorithms, validating strategies, or building backtesting infrastructure.