We've adapted this from GitHub's Ruby style guide. Feel free to make a pull request to add to this guide!
-
Use
snake_casefor methods and variables. -
Use
TitleCasefor classes and modules.† -
Use
SCREAMING_SNAKE_CASEfor other constants. -
The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e.
Array#empty?). -
The names of potentially "dangerous" methods (i.e. methods that modify
selfor the arguments,exit!, etc.) should end with an exclamation mark. Bang methods should only exist if a non-bang method exists. (More on this).
†This includes acronymns like HTML, XML, and RTC, which then become Html, Xml, and Rtc. Why? Rails 6 introduced Zeitwerk as its autoloader, which starts with the names of files and looks for appropriately-named constants to be defined within them. So html_parser.rb is inferred to define the constant HtmlParser. This is a departure from pre-Rails 6 behavior ... and previous versions of our style guide!
-
Use spaces around operators, after commas, colons, and semicolons, around
{and[and before}and].sum = 1 + 2 a, b = 1, 2 1 > 2 ? false : true; puts "Hi" [ 1, 2, 3 ].each { |e| puts e }
-
Don't put spaces within
(...).some(arg).other
-
Don't put spaces after
!.!array.include?(element)
-
Don't put spaces before
(when calling a method.# bad f (3 + 2) + 1 # good f(3 + 2) + 1
-
Don't put spaces around the
=operator when assigning default values to method parameters.# bad def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something... end # good def some_method(arg1=:default, arg2=nil, arg3=[]) # do something... end
-
Put spaces within the definition of a class or module; but snuggle modules that act as namespaces,
includes, and (optionally)attr_accessor,attr_reader, andattr_writer:module Namespace class SomeClass include SomeConcerns def initialize end end end
class TextContainer attr_reader :text def initialize(text) @text = text end end
-
Indent
whenas deep ascase.case when song.name == "Misty" puts "Not again!" when song.duration > 120 puts "Too long!" when Time.now.hour > 21 puts "It's too late" else song.play end
-
Indent
public/protected/privatekeywords as deep asclass.Why? These keywords apply — not just to the next method — but to all subsequent ones. They need to stand out from the level of the
defstatements.class SomeClass def public_method # ... end private def private_method # ... end end
-
rescueblocks should be outdented from the method blockdef some_method_with_rescue do_something_that_could_throw_exception! rescue i_did_something_funny end
-
When method calls are chained, put each on a line and indent with two spaces.
# good [ 1, 2, 3 ] .map { |x| x * x } .concat([ 10, 11 ]) .select { |x| x < 11 } .reduce { |x, y| x + y }
-
Use
defwith parentheses when the method takes arguments. Omit the parentheses when it doesn't.def some_method # ... end def some_method_with_arguments(arg1, arg2) # ... end
-
Don't use parentheses around the condition of an
if/unless/while.# bad if (x > 10) # ... end # good if x > 10 # ... end
-
If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation.
# bad f (3 + 2) + 1 # good f((3 + 2) + 1)
-
Avoid
returnwhere not required. However, a great place to use an explicitreturnis with anif/unlessin the postfix position as a guard clause.# bad def size_of(list) return list.size end # good def size_of(list) list.size end # good def size_of(list) return 0 unless list.respond_to?(:size) list.size end
-
Avoid explicit use of
selfas the recipient of internal class or instance messages unless assigning a value (withoutself, Ruby will create a local variable).class Person attr_accessor :name, :address # bad def mailing_label [self.name, self.address].join "\n" end # good def mailing_label [name, address].join "\n" end # bad (declares a new local variable) def move_to(new_address) address = new_address end # good def move_to(new_address) self.address = new_address end end
-
Prefer string interpolation to concatenation:
# bad email_with_name = user.name + " <" + user.email + ">" # good email_with_name = "#{user.name} <#{user.email}>"
-
Use double-quoted strings.
Why? Interpolation and escaped characters will always work without a delimiter change, and
'is a lot more common than"in string literals.# bad name = 'Bozhidar' # good name = "Bozhidar"
-
Never use
thenfor multi-lineif/unless.# bad if some_condition then # ... end # good if some_condition # ... end
-
Avoid the ternary operator (
?:) except in cases where all expressions are extremely trivial. However, do use the ternary operator overif/then/else/endconstructs for single line conditionals. (Never use the ternary operator for multi-line conditionals)# bad result = if some_condition then something else something_else end # good result = some_condition ? something : something_else
-
Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer
if/elseconstructs in these cases.# bad some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # good if some_condition nested_condition ? nested_something : nested_something_else else something_else end
-
Favor modifier (postfix)
if/unlessusage when you have a single-line body.# bad if some_condition do_something end # good do_something if some_condition
-
Never use
unlesswithelse. Rewrite these with the positive case first.# bad unless success? puts "failure" else puts "success" end # good if success? puts "success" else puts "failure" end
-
Never use
for, unless you know exactly why. Use iterators (e.g.each) instead.foris implemented in terms ofeach(so you're adding a level of indirection), but with a twist -fordoesn't introduce a new scope (unlikeeach) and variables defined in its block will be visible outside it.# bad for elem in arr do puts elem end # good arr.each do |elem| puts elem end
-
Use
&&and||, notandandor. -
Prefer symbols to strings as hash keys (and prefer the JSON-style syntax over the hashrocket syntax).
# ok hash = { "one" => 1, "two" => 2, "three" => 3 } # ok hash = { :one => 1, :two => 2, :three => 3 } # best hash = { one: 1, two: 2, three: 3 }
-
Use
%wand%ifreely for arrays of short strings and symbols, respectively.STATES = %w{ draft open closed } KEYS = %i{ here there everywhere }
-
Use
xmodifier for complex regular expressions. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored.regexp = %r{ start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end }x
-
Keyword arguments are recommended but not required when a method's arguments may otherwise be opaque or non-obvious when called. Additionally, prefer them over the old "Hash as pseudo-named args" style from pre-2.0 ruby.
So instead of this:
def remove_member(user, skip_membership_check=false) # ... end # Elsewhere: what does true mean here? remove_member(user, true)
Do this, which is much clearer.
def remove_member(user, skip_membership_check: false) # ... end # Elsewhere, now with more clarity: remove_member user, skip_membership_check: true
-
Use
Regexp.unionto create a regular expression from an array of terms.words = %w{ hey hi hello yo } # bad regexp = Regexp.new(words.join("|")) # good regexp = Regexp.union(words)
-
Treat empty methods like other methods
# bad def example; end # good def example end
-
Prefer
{...}overdo...endfor single-line blocks and blocks that return a value# bad names.select do |name| name.start_with?("S") end # good names.select { |name| name.start_with?("S") }
-
Prefer single-line
{...}blocks when several blocks are chained.# bad names.select do |name| name.start_with?("S") end.map { |name| name.upcase } # good names .select { |name| name.start_with?("S") } .map { |name| name.upcase }
-
Prefer multiline
do...endfor blocks that do control flow or method definitions.File.open("~/Desktop/diary.txt") do |f| f.write "I love Ruby so much" end task :escape do system "sudo shutdown now" end
-
Prefer the symbol shorthand to spelling out a proc that calls a method on its argument.
# ok names.map { |name| name.upcase } # better names.map(&:upcase)
-
Use
_for unused block parameters.# ok names = results.map { |name, age, favorite_marvel_movie| name } # better names = results.map { |name, _, _| name }
-
Don't use the
===(threequals) operator to check types.Why?
===is mostly an implementation detail to support Ruby features likecase, and it's not commutative. For example,String === "hi"is true and"hi" === Stringis false. Instead, useis_a?orkind_of?if you must. Refactoring is even better. It's worth looking hard at any code that explicitly checks types. -
Avoid the usage of class (
@@) variables. (Prefer class instance variables)Why? All the classes in a class hierarchy actually share one class variable. This often has unintended results.
class Parent @@class_var = "parent" def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = "child" end Parent.print_class_var # => will print "child"
-
Prefer
<<to concatenate strings.Why?
String#<<mutates the string instance in-place and is always faster thanString#+, which creates a bunch of new string objects.# good and also fast html = "" html << "<h1>Page title</h1>" paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
Be careful with
^and$as they match start/end of line, not string endings. If you want to match the whole string use:\Aand\z.string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # don't match