Ruby 💎

Hero image for Ruby 💎

Linting

We use RuboCop for automated code checks and most of our guidelines comply with the default guidelines set by the analyzer.

Formatting

  • Use soft-tabs with a two space indent.
  • Limit each line of code to fewer than 130 characters.
  • Use a single empty line to break between statements to organize logical chunks of code.
  • End each file with a newline.

Naming

  • Use snake_case for symbols, methods and variables.
# Bad
:someSymbol
someVar = 5

def someMethod
  # some code
end

# Good
:some_symbol

some_var = 5

def some_method
  # some code
end
  • Do not separate numbers from letters on symbols, methods and variables.
# Bad
:some_sym_1
some_var_1 = 1

def some_method_1
  # some code
end

# Good
:some_sym1

some_var1 = 1

def some_method1
  # some code
end
  • Use CamelCase for classes and modules.
# Bad
class Someclass
  # some code
end

class Some_Class
  # some code
end

# Good
class SomeClass
  # some code
end
  • Use snake_case for naming files.
# Bad
HelloWorld.rb

# Good
hello_world.rb
  • Use snake_case for naming directories.
# Bad
lib/HelloWorld/hello_world.rb

# Good
lib/hello_world/hello_world.rb
  • Aim to have just a single class/module per source file. Name the file name as the class/module, but replacing CamelCase with snake_case.

  • Use SCREAMING_SNAKE_CASE for other constants.

# Bad
SomeConst = 5

# Good
SOME_CONST = 5
  • The names of predicate methods (methods that return a boolean value) should end in a question mark ?.
# Bad
def is_true
end

# Good
def true?
end
  • The names of method which could raise exception should end in bang !.
# Bad
def authenticate
  raise UserNotAuthenticated unless authenticate_user(current_user)
end

# Good
def authenticate!
  raise UserNotAuthenticated unless authenticate_user(current_user)
end

Syntax

  • Use the proc invocation shorthand when the invoked method is the only operation of a block.
# Bad
names.map { |name| name.upcase }

# Good
names.map(&:upcase)
  • Prefer {...} over do...end for single-line blocks. Avoid using {...} for multi-line blocks.
names = %w[Bozhidar Steve Sarah]

# Bad
names.each do |name|
  puts name
end

# Good
names.each { |name| puts name }
  • Use .call explicitly when calling lambdas.
# Bad
lambda.(x, y)

# Good
lambda.call(x, y)
  • When defining an object of any mutable type meant to be a constant, make sure to call freeze on it. Common examples are strings, arrays, and hashes.
# Bad
class Color
  RED = 'red'
  BLUE = 'blue'
  GREEN = 'green'

  ALL_COLORS = [
    RED,
    BLUE,
    GREEN,
  ]

  COLOR_TO_RGB = {
    RED => 0xFF0000,
    BLUE => 0x0000FF,
    GREEN => 0x00FF00,
  }
end

# Good
class Color
  RED = 'red'.freeze
  BLUE = 'blue'.freeze
  GREEN = 'green'.freeze

  ALL_COLORS = [
    RED,
    BLUE,
    GREEN,
  ].freeze

  COLOR_TO_RGB = {
    RED => 0xFF0000,
    BLUE => 0x0000FF,
    GREEN => 0x00FF00,
  }.freeze
end

Conditional Expressions

  • Never negate the conditional of an if block. Use unless instead.
# Bad
if !false then
  #...
end

# Good
unless false
  #...
end
  • Never use the return value of = in conditionals.
# Bad
if is_branded = array.includes('brand')
  #...
end

# Good
is_branded = array.includes('brand')

if is_branded
  #...
end
  • Prefer using guard clauses over if/else and ternary operators.
# Bad
def message
   if valid?
     'success'
   else
     'error'
   end  
end

def message
  valid? ? 'success' : 'error'
end

# Good
def message
  return 'success' if valid?

  'error'
end
  • Never use nested conditionals for flow of control.

    The general principles boil down to:

    • Return immediately once you know your function cannot do anything more.
    • Reduce nesting and indentation in the code by returning early. This makes the code easier to read and requires less mental bookkeeping on the part of the reader to keep track of else branches.
    • The core or most important flows should be the least indented.
# Bad
def compute
  server = find_server
  if server
    client = server.client
    if client
      request = client.make_request
      if request
         process_request(request)
      end
    end
  end
end

# Good
def compute
  server = find_server
  return unless server

  client = server.client
  return unless client

  request = client.make_request
  return unless request

  process_request(request)
end

Refer to the section “Guard Clause”, p68-70 in Beck, Kent. Implementation Patterns. Upper Saddle River: Addison-Wesley, 2008.

Collections

  • Prefer %w to the literal array syntax for an array of raw strings.
# Bad
STATES = ["draft", "open", "closed"]

# Good
STATES = %w(draft open closed)
  • Use symbols instead of strings as hash keys.
# Bad
hash = { "one" => 1, "two" => 2, "three" => 3 }

# Good
hash = { one: 1, two: 2, three: 3 }
  • Use Hash#key? instead of Hash#has_key? and Hash#value? instead of Hash#has_value?. According to Matz, the longer forms are considered deprecated.
# Bad
hash.has_key?(:test)
hash.has_value?(value)

# Good
hash.key?(:test)
hash.value?(value)

Methods

  • Use parentheses in method definitions only if it accepts any parameters.
def method_without_params
  # body omitted
end

def method_with_parameters(param1, param2)
  # body omitted
end
  • Avoid usage of positional arguments. Use keyword arguments or an options hash instead.
# Bad
def positional_method(a = 1, b = 2, c, d)
  # method body
end

# Good
def method_with_keyword_arguments(a:, b:, c:, d:)
end

# Good
def method_with_options(options = {})
end

Modules

  • Use modules to organize a chunk of common stateless operations.

  • Use :: only to reference constants and constructors. Do not use :: for regular method invocation.

# Bad
Klass::some_method
object::some_method

# Good
Klass.some_method
object.some_method
  • Properly choose between def self, extend self and module_function.

Let’s consider a module Math implementing a method pi.

Use def self.pi

include Math will not give you any sort of accessor to .pi, not YourClass.pi, not instance.pi, nothing. You must use Math.pi.

Use extend self with public methods

include Math will give your instances a public instance method .pi.

Use extend self with private methods

include Math will give you a private instance method .pi but Math.pi will say “private method pi called”

Use module_function :pi

include Math will give your instances a private .pi method and Math.pi still works just fine.

Classes

  • Code in Ruby classes must follow the following structure:
# requires
require 'dependency_filename'

class KlassName
  # extend and include
  extend SomeModule
  include AnotherModule

  # constants
  SOME_CONSTANT = '20'.freeze

  # attribute macros
  attr_reader :name

  # additional macros (if any)
  validates :name

  # public class methods
  class << self
    def some_method
      #...
    end
  end

  # initialization
  def initialize
  end

  # public instance methods
  def some_method
  end

  # Group alias methods at the end of public methods
  alias alias_name some_method
  alias alias_alternative_name some_method

  # protected methods
  protected

  def some_protected_method
  end

  # private methods
  private

  def some_private_method
  end
end
  • Avoid nesting multiple classes within classes. Instead maintain each in their own file in a folder named like the containing class.
# Bad

# parent.rb
class Parent
  class Child
  end
end

# Good

# parent.rb
class Parent
end

# parent/child.rb
class Parent
  class Child
  end
end
  • Classes should be used only when it makes sense to create instances out of them. Prefer modules to classes with only class methods.
# Bad
class SomeClass
  class << self
    def self.some_method
      #...
    end
    
    def self.some_other_method
      #...
    end
  end
end

# Good
module SomeModule
  def some_method
    #...
  end

  def some_other_method
    #...
  end
end
  • Use class << self to define class methods instead of self..
# Bad
class SomeKlass 
  def self.method_name
    #...
  end
end

# Good 
class SomeKlass
  class << self
    def method_name
      #...
    end
  end
end
  • Try to make your classes as SOLID as possible.

  • Use the attr family of functions to define trivial accessors.

# Bad
class Klass
  def initialize(a, b)
    @a = a
    @b = b
  end

  def a
    @a
  end

  def b
    @b
  end
end

# Good
class Klass
  attr_reader :a, :b

  def initialize(a, b)
    @a = a
    @b = b
  end
end

Exceptions

  • Never rescue the Exception class. Rescue specific error classes.
# Bad
begin
  1/0
rescue Exception
  # exception handling
end

# Good
begin
  1/0
rescue ZeroDivisionError
  # exception handling
end

Monkey Patching/Refinements

  • Avoid monkey patching built-in classes, use refinements instead
# Bad
class Hash
  def deep_transform_camelize_keys
    deep_transform_keys { |key| key.to_s.camelize(:lower) }
  end
end

# Good
module HashExtensions
  refine Hash do
    def deep_transform_camelize_keys
      deep_transform_keys { |key| key.to_s.camelize(:lower) }
    end
  end
end