Ruby Style Guide

The personal style guide of Xavier Shay. Any potential exceptions must be justified and documented. Code examples can be revealed by clicking ↩ , and these imply more rules than are explicitly labeled. Rules that I often use editor features or scripts to apply are notated with ⚙ and a link to details.

Older code bases should migrate to this style, rather than stay consistent with themselves. Style upgrades should be in separate commits from behaviour changes.

The canonical URL for this document is http://xaviershay.com/ruby-style-guide.

Arrays

  • Use a trailing comma on final entry of multi-line definitions.
right = [
  1,
  2,
]

wrong = [
  1,
  2
]
right = %w[apples oranges]
wrong = %w(apples oranges)
wrong = ['apples', 'oranges']
right = %i[apples oranges grapes mangoes]
wrong = [:apples, :oranges, :grapes, :mangoes]

Assignment

class Right
  attr_writer :x, :y

  def initialize
    @x = []
    @y = []
  end
end

class Wrong
  attr_writer :x, :y

  def initialize
    self.x = []
    self.y = []
  end
end
right = true
42 if right

42 if wrong = true
if (right = big_calculation)
if wrong = big_calculation
if right == big_calculation
x, y = "right", 42
long_name_one, long_name_two = right_method

long_name_one, long_name_two = "wrong", 0

Blocks and Procs

right = [].map {|x|
  x == 2
}.size

wrong = [].map do |x|
  x == 2
end.size
right = ->(x) { x }
wrong = -> x { x }
wrong = lambda {|x| x }
right = ->{ }
wrong = ->() { }
right = [].map(&:length)
wrong = [].map {|x| x.length }
right = ->{ }.()
wrong = ->{ }.call
wrong = ->{ }[]

Classes

Right = Class.new(StandardError)
class Wrong < StandardError; end
Right = Struct.new(:value) do
  def squared; value * value end
end

class Wrong < Struct.new(:value)
  def squared; value * value end
end
right = Number[3]
wrong = Number.new(3)
class Namespace
  class Right; end
end

class Namespace::Wrong; end

Conditionals

right = flag ? 42 : 36
right = if flag && other_flag
  long_name_one
else
  long_name_two
end

wrong = flag && other_flag ? long_name_one : long_name_two
right if a && b
wrong if a and b

Dependencies

def my_method; MyCollaborater.does_something end

it do
  collaborator = class_double("MyCollaborator").as_stubbed_const
  collaborator.should_receive(:does_something)

  my_method
end
def my_method(client = Service::Client); client.echo('hello') end

it do
  client = Service::FakeClient.new
  my_method(client)

  expect(client.echoes).to eq(['hello'])
end

Hashes

right = {
  a: 1,
  b: 2,
}

wrong = {
  a: 1,
  b: 2
}
right = {
  :a   => 1,
  'ab' => 2,
}

wrong = {
  a: 1,
  'abc' => 2,
}
right = (1..10).each_with_object({}) {|x, h| h[x] = x ** 2 }
wrong = Hash[(1..10).map {|x| [x, x ** 2] }]
wrong = (1..10).reduce({}) {|h, x| h.update(x => x ** 2) }
right(a: 1)
wrong({a: 1})
right = {}.fetch(:a, 0)
wrong = {}[:a] || 0

Enumeration

right.map     { ... }
wrong.collect { ... }

right.reduce { ... }
wrong.inject { ... }

right.find   { ... }
wrong.detect { ... }

right.select   { ... } # Select is an exception for symmetry with `reject`.
wrong.find_all { ... }

right.reject { ... }
wrong.there_is_no_wrong_way_to_reject { ... }

Line Length

def right_method(long_param_one,
                 long_param_two,
                 long_param_three,
                 long_param_four)
  42
end

def wrong_method(long_param_one, long_param_two, long_param_three, long_param_four)
  42
end
<<-EOS.strip.gsub(/\s+/, ' ')
  But we refuse to believe that the bank of justice is bankrupt. We refuse to
  believe that there are insufficient funds in the great vaults of opportunity
  of this nation. And so we have come to cash this check, a check that will
  give us upon demand the riches of freedom and the security of justice.
EOS
right = "%s thinks that %s should %s" % [
  long_variable_one,
  long_variable_two,
  long_variable_three
]

wrong = "#{long_variable_one} thinks that #{long_variable_two} should #{long_variable_three}"
right = (1..10)
  .map {|x| x + 1 }
  .select(&:odd?)

wrong = (1..10).map {|x| x + 1 }.select(&:odd?)

Methods

def right(*args)
end

def wrong *args
end
def top;    @top    ||= @rect.top    end
def right;  @right  ||= @rect.right  end
def bottom; @bottom ||= @rect.bottom end
def left;   @left   ||= @rect.left   end

def wrong
  @wrong ||= []
end
def right(x, _); x end
def wrong(x, y); x end
def right(*); 42 end
def wrong(_, _); 42 end
object.right(17, 23)
object.wrong 17, 23
puts right(42)
puts wrong 42
object.right
object.wrong()
do_the right thing
do_the(wrong thing)

puts :right
puts(:ymmv)

Naming

right = bird_names.map {|x| x.to_s.length }
wrong = bird_names.map {|bird_name| bird_name.to_s.length }
right = v * t + 0.5 * a * t ** 2
wrong = velocity * time + 0.5 * acceleration * time ** 2
def valid?; "right" end
def is_valid?; "wrong" end
def has_valid?; "wrong" end
Example::Right
WrongExample
right = bird.name
wrong = bird.bird_name

Nil values

right(possibly_nil_value || default_value)
wrong(possibly_nil_value ? possibly_nil_value : default_value)
right ||= big_calculation
wrong = big_calculation unless wrong
right = object && object.name
wrong = object ? object.name : nil
wrong = object.name if object

Regexes

right = %r{/some/path}
wrong = /\/some\/path/
right = "hello\nthere" =~ /\Ahello\z/
wrong = "hello\nthere" =~ /^hello$/
right = "http://example.com"[%r{\A(http(s)?)://}, 1]
wrong = %r{(http(s)?)://}.match("http://")[1]

Whitespace

def my_method
  if rand < 0.5
    if rand > 0.5
      42
    end
  end
end
class Right
  private

  def my_method; 42 end
end

class Wrong
  private

    def my_method; 42 end
end
right = if rand < 0.5
  0
else
  1
end

wrong = if rand < 0.5
          0
        else
          1
        end
right = {
  'a'   => 1,
  'ab'  => 2,
  'abc' => 3,
}

wrong = {
  'a' => 1,
  'ab' => 2,
  'abc' => 3,
}
right = {
  a:   1,
  ab:  2,
  abc: 3,
}

wrong = {
  a: 1,
  ab: 2,
  abc: 3,
}
a  = "right"
ab = "right"

a = "wrong"
ab = "wrong"
right = [].map {|x| x }
wrong = [].map { |x| x }
def right(x); end
def wrong (x); end
def wrong( x ); end
right(42)
wrong (42)
wrong( 42 )

h