Ruby Conventions (Rootstrap)
Apply these conventions whenever producing or modifying Ruby code. Full guide: https://github.com/rootstrap/tech-guides/blob/master/ruby/README.md
Most of these are enforced by RuboCop (see .rubocop.yml). When in doubt, run RuboCop.
Source Code Layout
- UTF-8, Unix line endings, 2-space indentation (no tabs), one expression per line (no
;). - Max 100 chars per line; end files with a newline; no trailing whitespace.
- Prefer
FooError = Class.new(StandardError)over emptyclass ... end. - Avoid single-line methods (exception:
def no_op; end). - Spaces around operators, after commas/colons; no spaces inside
(,[, around!, or in range literals (1..3). - Exception: no spaces around exponent:
c**2. - Indent
whenthe same ascase. - Blank lines between methods and around access modifiers; no blank lines inside class/method bodies.
- No trailing comma after last arg/element.
- Spaces around
=in default params:def f(a = 1). - Avoid line continuation
\except for string concatenation. - In multi-line chains, keep
.on the next line. - Use underscores in big numbers:
1_000_000. Lowercase prefixes0x,0o,0b. - No block comments (
=begin/=end). - Never more than one consecutive blank line.
- Align multi-line method args after
(, OR single-indent with)on its own line (avoid "double indent"). - For
case/ifresult assignment, either align branches under the keyword or break withkind =on its own line.
Syntax
- Use
::only for constants/constructors, not method calls. defwith parens when params exist, omit when empty.- Parens around method arguments, except: no-arg calls, DSL methods (
validates :name, ...), and keyword-like (attr_reader,puts). - Optional args at end of parameter list.
- Prefix unused block vars with
_:|_k, v|. - Avoid parallel assignment (
a, b, c = 1, 2, 3) except for swap, method-return destructuring, or splat. - Prefer trailing-underscore form:
a, = foo.split(',')overa, _, _ = .... Use named underscore vars (_second) when position conveys meaning. - Don't use
for; use iterators (each). - No
thenin multi-lineif; noif x; .... Always put the condition on the same line asif/unless. - Avoid multi-line ternary — use
if/unlessinstead. - No
while cond do/until cond dofor multi-line loops (drop thedo). - Favor ternary over
if/then/elseone-liners; don't nest ternaries. - Leverage
if/caseas expressions. - Use
!notnot; avoid!!boolean coercion. and/orkeywords are banned — use&&/||.- Favor modifier
if/unless/while/untilfor single-line bodies; avoid on multi-line blocks; don't nest modifiers. - Favor
unlessfor negatives; favoruntiloverwhilefor negative loop conditions. Neverunlesswithelse. - No parens around control-expression conditions (except safe assignment
if (v = ...)). - Use
Kernel#loopfor infinite loops; preferloop { ... break unless ... }overbegin ... end whilefor post-condition loops. - Omit outer
{}AND parens for internal DSL methods:validates :name, presence: true, length: { within: 1..10 }. - Omit outer
{}on trailing options hashes:user.set(name: 'John', age: 45). - Use
&:methodshorthand:names.map(&:upcase). {...}for single-line blocks,do...endfor multi-line. Avoiddo...endin chains.- Avoid explicit
returnwhen unnecessary; avoidself.unless required. - Use
||=to init nil/unset vars; don't use||=for booleans. - Use
&&=to preprocess nullable values. - Avoid
===outsidecase. - Use
==noteql?unless strict type comparison is intended. - No space between method name and opening paren:
f(x). - No nested method defs; use lambdas.
- Lambda:
->(a, b) { ... }with args (parens required);-> { ... }with no args (omit parens);lambda do ... endfor multi-line. - Prefer
procoverProc.new; use.call()not[]or.(). - Use shorthand self-assignment:
x += y,x **= y, etc. - Use explicit
&blockto forward blocks rather than wrapping them. - Don't shadow methods with local variables (e.g. naming an arg
optionswhen an accessor already exists). - Don't use character literals (
?x) — use'x'. - Avoid Perl-style special vars (
$;,$,); preferEnglishlibrary aliases ($LOAD_PATH, etc.). - Don't use
BEGIN/ENDblocks; useKernel#at_exitinstead. - Use
warnover$stderr.puts. - Favor
sprintf/formatoverString#%;Array#joinoverArray#*. - Use
Array(var)to coerce possibly-single values into arrays. - Use ranges or
between?instead ofx >= a && x <= b. - Predicate methods (
.even?,.zero?,.nil?) over== 0,== nil. - Avoid
!x.nil?whenif xsuffices. - Guard clauses over nested conditionals;
nextoverifin loops. - Prefer:
mapovercollect,selectoverfind_all,findoverdetect,reduceoverinject,sizeoverlength/count(note:counton non-Array Enumerables iterates the full collection). flat_mapovermap.flatten(1);reverse_eachoverreverse.each.
Naming
- Identifiers in English,
snake_casefor methods/vars/symbols/files/dirs. CamelCasefor classes/modules; keep acronyms uppercase (XMLParser).SCREAMING_SNAKE_CASEfor constants.- Predicate methods end with
?; do not prefix withis_/can_/does_. - Bang methods (
!) exist only when a safe counterpart exists. - Name binary operator params
other(exceptions:<<and[]). - No numeric separation:
some_var1notsome_var_1. - One class/module per file, file named
snake_caseafter the class/module. - Define non-bang methods in terms of bang when possible:
def flatten_once; dup.flatten_once!; end.
Comments
- Prefer self-documenting code; comments in English, capitalized, one space after
#. - Refactor bad code instead of explaining it.
- Avoid superfluous comments (
counter += 1 # Increments counter by one). - Keep comments up to date — an outdated comment is worse than none.
- Annotations:
TODO:+ description, above the relevant code. Add other annotation tags only after documenting them as project conventions. - Magic comments (
# frozen_string_literal: true) at top, one per line, blank line before code.
Classes & Modules
- Layout order:
extend/include→ inner classes → constants →attr_*→ other macros → public class methods →initialize→ public instance methods → protected → private. - Separate
includeper mixin. - Don't nest multi-line classes; use matching folder structure.
- Prefer modules (
extend self) over classes with only class methods. - Use
def self.method(notdef ClassName.method). - Within class methods calling siblings, omit
self.. - Always supply
to_sfor domain objects. - Use
attr_reader/attr_accessor; avoidattr; don't prefix withget_/set_. Struct.newfor trivial value objects; don't inherit from it.- Avoid class variables (
@@var); prefer class instance variables. - Proper visibility (
private/protected); indent modifiers at method level with blank lines. - Prefer composition over inheritance. Use duck typing where appropriate.
- Apply SOLID principles; respect the Liskov Substitution Principle (subclasses should be substitutable for their parents).
- Encourage factory methods for clearer object creation APIs.
- Use
aliasin lexical scope;alias_methodfor runtime/module aliasing. Note:aliasbinds at definition time, so subclass overrides won't be picked up unless re-aliased.
Exceptions
- Prefer
raiseoverfail;raise SomeException, 'message'(not.new(...), notRuntimeError). - Never
returnfromensure. - Use implicit
beginin method bodies (def foo ... rescue ... end). - Don't suppress exceptions; avoid
rescuein modifier form. - No exceptions for flow control.
- Never
rescue Exception— userescue StandardError => eor barerescue => e. - Specific exceptions higher in the rescue chain.
- Release resources in
ensureor block form (File.open('f') { |f| ... }). - Favor stdlib exceptions over new classes.
- Extract repeated rescue patterns into contingency methods (
with_io_error_handling { ... }) to DRY up error handling.
Collections
- Use literals
[]and{}(notArray.new,Hash.new). %w[one two three]for word arrays,%i[a b c]for symbol arrays (2+ elements).- Prefer
first/lastover[0]/[-1];Setfor unique collections. - Symbols as hash keys; 1.9 syntax
{ one: 1 }; don't mix with hash rockets. Hash#key?nothas_key?;Hash#each_keynotkeys.each.Hash#fetchfor required keys; block form for expensive defaults:hash.fetch(:k) { expensive }.Hash#values_atfor multi-key lookup.- Don't mutate a collection while iterating.
- Don't use mutable objects as hash keys.
- Rely on ordered hashes (Ruby 1.9+); insertion order is preserved.
- When providing collection-returning APIs, offer an alternate accessor to avoid
nil[]: preferRegexp.last_match(1)overRegexp.last_match[1].
Numbers
Integer(notFixnum/Bignum) for type checks.rand(1..6)overrand(6) + 1.
Strings
- Interpolation
"#{x}"over concatenation. - Pick single or double quotes consistently (guide prefers single when no interpolation).
{}around@var/$varin interpolation; don't call.to_sinside.String#<<to build large strings, not+=.sub/trovergsubwhen simpler.- Squiggly heredocs
<<~ENDfor multi-line indented strings.
Date & Time
- In Rails code, use
Time.current,Time.zone.now, orTime.zone.parse; avoidTime.nowandTime.parse. - In non-Rails Ruby code, prefer
Time.nowoverTime.new; useDateorTime, notDateTime.
Regular Expressions
- Plain string ops (
string['text']) over regex when possible; alsostring[/regexp/]andstring[/text(grp)/, 1]forms. (?:...)for non-capturing; named groups(?<name>...)over numbered.Regexp.last_match(n)not$1.\A/\z(not^/$) for full-string boundaries./xmodifier for complex, commented regexes.- In character classes
[], only^,-,\,]need escaping; don't escape.or brackets. - Use
sub/gsubwith a block or hash for complex replacements.
Percent Literals
%()only for single-line strings needing both interpolation and". Heredocs for multi-line.- Avoid
%q()unless the string has both'and". %r{...}only when the regex contains/.- Brackets:
()for strings,[]for%w/%i,{}for%r. - Avoid
%xunless invoking a command whose string contains backticks. - Avoid
%s— prefer:"some string"for symbols needing spaces.
Metaprogramming
- Avoid needless metaprogramming; don't monkey-patch core classes in libraries.
- Prefer block
class_evalover string form; preferdefine_method. - If you must use string-form
class_eval/eval, pass__FILE__and__LINE__for sensible backtraces. - Avoid
method_missing; if needed, also definerespond_to_missing?, callsuper, and only catch well-defined prefixes (e.g.find_by_*) — delegating to non-magical methods. public_sendoversend;__send__oversendwhen receiver may definesend.
Misc
- Write
ruby -wclean code (run with warnings). - Avoid hashes as optional params (except initializers).
- Keep methods small (~10 LOC, ideally <5); params 3–4 max.
- Avoid more than 3 levels of block nesting.
- Code functionally; don't mutate parameters unless that's the method's purpose.
- Prefer module instance variables over globals (
$foo). - Use
OptionParserfor complex CLI options;ruby -sonly for trivial cases. - If adding global methods, put them in
Kerneland make themprivate. - Be consistent and use common sense — within a file, prefer matching the surrounding style over strict rule-following.