Syntax
Use
::only to reference constants(this includes classes and modules). Do not use::for regular method invocation. [link]# bad SomeClass::some_method some_object::some_method # good SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONSTUse
defwith parentheses when there are parameters. Omit the parentheses when the method doesn't accept any parameters. [link]# bad def some_method() # body omitted end # good def some_method # body omitted end # bad def some_method_with_parameters param1, param2 # body omitted end # good def some_method_with_parameters(param1, param2) # body omitted endDefine optional arguments at the end of the list of arguments. Results can be surprising when calling methods that have optional arguments at the front of the list. [link]
# bad def some_method(a = 1, b = 2, c, d) puts "#{a}, #{b}, #{c}, #{d}" end some_method("w", "x") # => "1, 2, w, x" some_method("w", "x", "y") # => "w, 2, x, y" some_method("w", "x", "y", "z") # => "w, x, y, z" # good def some_method(c, d, a = 1, b = 2) puts "#{a}, #{b}, #{c}, #{d}" end some_method("w", "x") # => "w, x, 1, 2" some_method("w", "x", "y") # => "w, x, y, 2" some_method("w", "x", "y", "z") # => "w, x, y, z"Avoid the use of parallel assignment for defining variables. Parallel assignment is allowed when it is the return of a method call, used with the splat operator, when used to swap variable assignment, or for creating obviously related data clumps. Parallel assignment is less readable than separate assignment. [link]
# bad a, b, c, d = "foo", "bar", "baz", "foobar" # good a = "foo" b = "bar" c = "baz" d = "foobar" # good: swapping variable assignment # Swapping variable assignment is a special case because it will allow you to # swap the values that are assigned to each variable. a = "foo" b = "bar" a, b = b, a puts a # => "bar" puts b # => "foo" # good: method return def multi_return [1, 2] end first, second = multi_return # good: use with splat first, *list = [1, 2, 3, 4] hello_array = *"Hello" a = *(1..3) # good: data clump x, y = 4, 2Do not use
for. Iterators should be used 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. [link]arr = [1, 2, 3] # bad for elem in arr do puts elem end # note that elem is accessible outside of the for loop elem # => 3 # good arr.each { |elem| puts elem } # elem is not accessible outside each's block elem # => NameError: undefined local variable or method `elem'Do not use
thenfor multi-lineif/unless. [link]# bad if some_condition then # body omitted end # good if some_condition # body omitted endAlways put the condition on the same line as the
if/unlessin a multi-line conditional. [link]# bad if some_condition do_something do_something_else end # good if some_condition do_something do_something_else endFavor the ternary operator(
?:) overif/then/else/endconstructs. It's more common and obviously more concise. [link]# bad result = if some_condition then something else something_else end # good result = some_condition ? something : something_elseUse one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer
if/elseconstructs in these cases. [link]# 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 endDo not use
if x; .... Use the ternary operator instead. [link]# bad result = if some_condition; something else something_else end # good result = some_condition ? something : something_elseLeverage the fact that
ifandcaseare expressions which return a result. [link]# bad if condition result = x else result = y end # good result = if condition x else y endUse
when x then ...for one-line cases. The alternative syntaxwhen x: ...has been removed as of Ruby 1.9. [link]Do not use
when x; .... See the previous rule. [link]Use
!instead ofnotfor boolean negation. [link]# bad: parentheses are required because of op precedence x = (not something) # good x = !somethingThe
and,or, andnotkeywords are for flow control, not conditional comparisons like&&,||, and!. They have a different operator precedence to support this usage. [link]# bad: conditional comparisons if some_condition and not some_other_condition do_something end # bad: flow control # same as: size = (ARGV.shift || abort "USAGE: #{$PROGRAM_NAME} SIZE") size = ARGV.shift || abort "USAGE: #{$PROGRAM_NAME} SIZE" # good: conditional comparisons if some_condition && !some_other_condition do_something end # good: flow control # same as: (size = ARGV.shift) or (abort "USAGE: #{$PROGRAM_NAME} SIZE") size = ARGV.shift or abort "USAGE: #{$PROGRAM_NAME} SIZE"Avoid multi-line
?:(the ternary operator); useif/unlessinstead. [link]Favor modifier
if/unlessusage when you have a single-line body. Another good alternative is the usage of flow controland/or/not. [link]# bad if some_condition do_something end # good do_something if some_condition # another good option some_condition and do_somethingAvoid modifier
if/unlessusage at the end of a non-trivial multi-line block. [link]# bad 10.times do # multi-line body omitted end if some_condition # good if some_condition 10.times do # multi-line body omitted end endAvoid nested modifier
if/unless/while/untilusage. Favor&&/||if appropriate. [link]# bad do_something if other_condition if some_condition # good do_something if some_condition && other_conditionFavor
unlessoverif !...for negative conditions (or flow controlor). [link]# bad do_something if !some_condition # bad do_something if not some_condition # good do_something unless some_condition # another good option some_condition or do_somethingDo not use
unlesswithelse. Rewrite these with the positive case first. [link]# bad unless success? puts "failure" else puts "success" end # good if success? puts "success" else puts "failure" endDon't use
unlesswith compound conditionals. [link]# bad unless question? || other_question? puts "confusing" end # good if !question? && !other_question? puts "easier to grok" end # good unless question? puts "simple" endDon't bury side effects in compound conditionals. [link]
# bad if cool_dude && user.save && wearing_sunglasses # ... end # good: explicit change if cool_dude user_saved = user.save if user_saved && wearing_sunglasses # ... end end # good: simple conditional if user.save # ... endDon't use parentheses around the condition of an
if/unless/while/until. [link]# bad if (x > 10) # body omitted end # good if x > 10 # body omitted end
Note that there is an exception to this rule, namely safe assignment in condition.
Do not use
while/until ... dofor multi-linewhile/until. [link]# bad while x > 5 do # body omitted end until x > 5 do # body omitted end # good while x > 5 # body omitted end until x > 5 # body omitted endFavor modifier
while/untilusage when you have a single-line body. [link]# bad while some_condition do_something end # good do_something while some_conditionFavor
untiloverwhile !...for negative conditions. [link]# bad do_something while !some_condition # good do_something until some_conditionUse
Kernel#loopinstead ofwhile/untilwhen you need an infinite loop. [link]# bad while true do_something end until false do_something end # good loop do do_something endUse
Kernel#loopwithbreakrather thanbegin/end/untilorbegin/end/whilefor post-loop tests. [link]# bad begin puts val val += 1 end while val < 0 # good loop do puts val val += 1 break unless val < 0 endOmit parentheses around parameters for methods that are part of an internal DSL (e.g. Rake, Rails, RSpec), methods that have "keyword" status in Ruby (e.g.
attr_reader,puts) and attribute access methods. Use parentheses around the arguments of all other method invocations. [link]class Person attr_reader :name, :age # omitted end temperance = Person.new("Temperance", 30) temperance.name puts temperance.age x = Math.sin(y) array.delete(e) bowling.score.should == 0Omit the outer braces around an implicit options hash. [link]
# bad user.set({name: "John", age: 45, permissions: { read: true }}) # good user.set(name: "John", age: 45, permissions: { read: true })Omit both the outer braces and parentheses for methods that are part of an internal DSL. [link]
class Person < ActiveRecord::Base # bad validates(:name, {presence: true, length: { within: 1..10 }}) # good validates :name, presence: true, length: { within: 1..10 } endOmit parentheses for method calls with no arguments. [link]
# bad Kernel.exit!() 2.even?() fork() "test".upcase() # good Kernel.exit! 2.even? fork "test".upcaseUse the proc invocation shorthand when the invoked method is the only operation of a block. [link]
# bad names.map { |name| name.upcase } # good names.map(&:upcase)Use
{...}when defining a block where the return value matters anddo...endwhen using a block for flow control or side effects. Many guides recommend{...}for single-line blocks anddo...endfor multiple lines, but you can already tell that at a glance and it's helpful to signal the intended usage instead. That said, single-linedo...endis awkward, so do split those across multiple lines, even if it's short. [link]names = %w[bozhidar steve sarah] # bad: block return value matters names.map do |name| name.capitalize end # good: map() always uses {...} because the return value always matters names.map { |name| name.capitalize } # bad: block return value is ignored; used for side effects (printing) names.each { |name| puts name.capitalize } # passable: awkward names.each do |name| puts name.capitalize end # good: each() always uses do...end because the return value never matters names.each do |name| puts name.capitalize end # bad: block return value matters headers = File.open("names.csv") do |f| f.gets end # bad: block used for flow control File.open("sorted_names.txt") { |f| f.each_cons(2) do |first, last| puts "#{first.strip} comes before #{last.strip}" end } # good: {...} and do...end clarify intent of open() usage headers = File.open("names.csv") { |f| f.gets } File.open("sorted_names.txt") do |f| f.each_cons(2) do |first, last| puts "#{first.strip} comes before #{last.strip}" end endConsider using explicit block argument to avoid writing block literal that just passes its arguments to another block. Beware of the performance impact, though, as the block gets converted to a Proc. [link]
require "tempfile" # bad def with_tmp_dir Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments end end # good def with_tmp_dir(&block) Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir, &block) end end with_tmp_dir do |dir| puts "dir is accessible as a parameter and pwd is set: #{dir}" endAvoid
returnwhere not required for flow control. [link]# bad def some_method(some_arr) return some_arr.size end # good def some_method(some_arr) some_arr.size endAvoid
selfwhere not required. (It is only required when calling a self write accessor.) [link]# bad def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end # good def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verified endAs a corollary, avoid shadowing methods with local variables unless they are both equivalent. [link]
class Foo attr_accessor :options # ok def initialize(options) self.options = options # both options and self.options are equivalent here end # bad def do_something(options = {}) unless options[:when] == :later output(self.options[:message]) end end # good def do_something(params = {}) unless params[:when] == :later output(options[:message]) end end endDon't use the return value of
=(an assignment) in conditional expressions unless the assignment is wrapped in parentheses. This is a fairly popular idiom among Rubyists that's sometimes referred to as safe assignment in condition. [link]# bad if v = array.grep(/foo/) do_something(v) ... end # good if (v = array.grep(/foo/)) do_something(v) ... end # passable v = array.grep(/foo/) if v do_something(v) ... endUse shorthand self assignment operators whenever applicable. [link]
# bad x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y # good x += y x *= y x **= y x /= y x ||= y x &&= yUse
||=to initialize variables only if they're not already initialized. [link]# bad name = name ? name : "Bozhidar" # bad name = "Bozhidar" unless name # good: set name to "Bozhidar", only if it's nil or false name ||= "Bozhidar"Don't use
||=to initialize boolean variables. (Consider what would happen if the current value happened to befalse.) [link]# bad: would set enabled to true even if it was false enabled ||= true # good enabled = true if enabled.nil?Use
&&=to preprocess variables that may or may not exist. Using&&=will change the value only if it exists, removing the need to check its existence withif. [link]# bad if something something = something.downcase end # bad something = something ? something.downcase : nil # ok something = something.downcase if something # passable something = something && something.downcase # good something &&= something.downcaseAvoid explicit use of the case equality operator
===. As its name implies it is meant to be used implicitly bycaseexpressions and outside of them it yields some pretty confusing code. [link]# bad Array === something (1..100) === 7 /something/ === some_string # good something.is_a?(Array) (1..100).include?(7) some_string =~ /something/Do not use
eql?when using==will do. The stricter comparison semantics provided byeql?are rarely needed in practice. [link]# bad: eql? is the same as == for strings "ruby".eql? some_str # good "ruby" == some_str 1.0.eql? x # eql? makes sense here if want to differentiate between Fixnum and Float 1Avoid using Perl-style special variables (like
$:,$;, etc.). They are quite cryptic and their use in anything but one-liner scripts is discouraged. Use the human-friendly aliases. Some aliases require theEnglishlibrary. The regular expression capture variables ($1,$2, etc.) are allowed. [link]# bad $:.unshift File.dirname(__FILE__) # good: English not needed $LOAD_PATH.unshift File.dirname(__FILE__) # good require "English" $FIELD_SEPARATOR = "," numbers = "1,2,3".split # good: regex capture variables "Gray, James" =~ /\A(\w+),\s*(\w+)\z/ first, last = $2, $1Do not put a space between a method name and the opening parenthesis. [link]
# bad f (3 + 2) + 1 # good f(3 + 2) + 1If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write
f((3 + 2) + 1). [link]Always run the Ruby interpreter with the
-woption so it will warn you if you forget either of the rules above! [link]Do not use nested method definitions, use lambda instead. Nested method definitions actually produce methods in the same scope (e.g. class) as the outer method. Furthermore, the "nested method" will be redefined every time the method containing its definition is invoked. [link]
# bad def foo(x) def bar(y) # body omitted end bar(x) end # good: the same as the previous, but no bar redefinition on every foo call def bar(y) # body omitted end def foo(x) bar(x) end # also good def foo(x) bar = ->(y) { ... } bar.call(x) endUse the new lambda literal syntax for single line body blocks. Use the
lambdamethod for multi-line blocks. [link]# bad l = lambda { |a, b| a + b } l.call(1, 2) # passable: awkward l = ->(a, b) { tmp = a * 7 tmp * b / 50 } # good l = ->(a, b) { a + b } l.call(1, 2) l = lambda { |a, b| tmp = a * 7 tmp * b / 50 }Don't omit the parameter parentheses when defining a stabby lambda with parameters. [link]
# bad l = ->x, y { something(x, y) } # good l = ->(x, y) { something(x, y) }Omit the parameter parentheses when defining a stabby lambda with no parameters. [link]
# bad l = ->() { something } # good l = -> { something }Prefer
procoverProc.new. [link]# bad p = Proc.new do |n| puts n end # good p = proc do |n| puts n endPrefer
proc.call()orproc[]overproc.()for both lambdas and procs. [link]# bad: uncommon syntax l = ->(v) do puts v end l.(1) # good l = ->(v) do puts v end l.call(1) # good: can substitute for places that expect a Hash l = ->(v) do puts v end l[1]Prefix with
_unused block parameters and local variables. It's also acceptable to use just_(although it's a bit less descriptive). This convention is recognized by the Ruby interpreter and tools like RuboCop and will suppress their unused variable warnings. [link]# bad result = hash.map { |k, v| v + 1 } def something(x) unused_var, used_var = something_else(x) # ... end # good result = hash.map { |_k, v| v + 1 } def something(x) _unused_var, used_var = something_else(x) # ... end # good result = hash.map { |_, v| v + 1 } def something(x) _, used_var = something_else(x) # ... endUse
$stdout/$stderr/$stdininstead ofSTDOUT/STDERR/STDIN.STDOUT/STDERR/STDINare constants, and while you can actually reassign (possibly to redirect some stream) constants in Ruby, you'll get an interpreter warning if you do so. [link]Use
warninstead of$stderr.puts. Apart from being more concise and clear,warnallows you to suppress warnings if you need to (by setting the warn level to 0 via-W0). [link]Favor the use of
sprintfand its aliasformatover the fairly crypticString#%method. [link]# bad "%d %d" % [20, 10] # => "20 10" # good sprintf("%d %d", 20, 10) # => "20 10" # good sprintf("%{first} %{second}", first: 20, second: 10) # => "20 10" format("%d %d", 20, 10) # => "20 10" # good format("%{first} %{second}", first: 20, second: 10) # => "20 10"Favor the use of
Array#joinover the fairly crypticArray#*with a string argument. [link]# bad %w[one two three] * ", " # => "one, two, three" # good %w[one two three].join(", ") # => "one, two, three"Use
Array()instead of an explicitArraycheck, when dealing with a variable you want to treat as anArray, but you're not certain it is one. [link]# bad paths = [paths] unless paths.is_a?(Array) paths.each do |path| do_something(path) end # good Array(paths).each do |path| do_something(path) endUse ranges or
Comparable#between?instead of complex comparison logic when possible. [link]# bad do_something if x >= 1000 && x <= 2000 # good do_something if (1000..2000).include?(x) # good do_something if x.between?(1000, 2000)Favor the use of predicate methods to explicit comparisons with
==. Numeric comparisons are OK. [link]# bad if x % 2 == 0 end if x % 2 == 1 end if x == nil end # good if x.even? end if x.odd? end if x.nil? end if x.zero? end if x == 0 endDon't do explicit non-
nilchecks unless you're dealing with boolean values. [link]# bad do_something if !something.nil? do_something if something != nil # good do_something if something # good: dealing with a boolean def value_set? !@some_boolean.nil? endAvoid the use of
BEGINblocks outside of one-liners. [link]Do not use
ENDblocks outside of one-liners. UseKernel#at_exitinstead. [link]# bad END { puts "Goodbye!" } # good at_exit do puts "Goodbye!" endAvoid the use of flip-flops outside of one-liners. [link]
Avoid use of nested conditionals for flow of control. [link]
Prefer a guard clause when you can assert invalid data. A guard clause is a conditional statement at the top of a function that bails out as soon as it can.
# bad def compute_thing(thing) if thing[:foo] update_with_bar(thing) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end end end # good def compute_thing(thing) return unless thing[:foo] update_with_bar(thing[:foo]) return re_compute(thing) unless thing[:foo][:bar] partial_compute(thing) endPrefer
nextin loops instead of conditional blocks.# bad [0, 1, 2, 3].each do |item| if item > 1 puts item end end # good [0, 1, 2, 3].each do |item| next unless item > 1 puts item endPrefer
mapovercollect,findoverdetect,selectoverfind_all, andinjectoverreduce. This is not a hard requirement; if the use of the alias enhances readability, it's ok to use it. The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use ofselectis encouraged overfind_allis that it goes together nicely withrejectand its name is pretty self-explanatory.injecthas been in Ruby longer and thus is just more widely used thanreduce. [link]Don't use
countas a substitute forsize. ForEnumerableobjects other thanArrayit will iterate the entire collection in order to determine its size. [link]# bad some_hash.count # good some_hash.sizeUse
flat_mapinstead ofmap+flatten. This does not apply for arrays with a depth greater than 2, i.e. ifusers.first.songs == ["a", ["b", "c"]], then usemap + flattenrather thanflat_map.flat_mapflattens the array by 1, whereasflattenflattens it all the way. [link]# bad all_songs = users.map(&:songs).flatten.uniq # good all_songs = users.flat_map(&:songs).uniqPrefer
reverse_eachtoreverse.eachbecause some classes thatinclude Enumerablewill provide an efficient implementation. Even in the worst case where a class does not provide a specialized implementation, the general implementation inherited fromEnumerablewill be at least as efficient as usingreverse.each. [link]# bad array.reverse.each do ... end # good array.reverse_each do ... end