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)
    
  • Use ... operator when forwarding method arguments to a method.

    # Bad
    def call(*args, **kwargs, &block)
      process(*args, **kwargs, &block)
    end
      
    # Good
    def call(...)
      process(...)
    end
    

    Ruby 3.0 has added support passing leading arguments along with ... operator which allows writing more flexible methods.

    def create_github_repo(project_type, ...)
      if project_type == 'open_source'
        initialize_public_repo(...)
      else
        initialize_private_repo(...)
      end
    end
    
  • 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"]
    
    # Less Preferred
    STATES = %w(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)
    

Strings

  • Prefer single quotes over double quotes.

    # Bad
    ruby = "Ruby is an interpreted, high-level, general-purpose programming language."
    
    # Good
    ruby = 'Ruby is an interpreted, high-level, general-purpose programming language.'
    
  • Prefer string interpolation over appending to strings.

    # Bad
    full_name = user.first_name + ' ' + user.last_name
    
    # Good
    full_name = "#{user.first_name } #{user.last_name}"
    
  • Prefer symbols over strings where applicable.

    # Bad
    person = {
      'name' => 'Yukihiro Matsumoto',
      'age' => 55
    }
    
    # Good
    person = {
      name: 'Yukihiro Matsumoto',
      age: 55
    }
    
    # Bad
    class User  
      attr_accessor 'first_name' 
      ...
    end
    
    # Good
    class User  
      attr_accessor :first_name 
      ...
    end
    
  • Prefer heredoc to multiline strings.

    # Bad
    notice = "Subscription expiring soon!.\n Your free trial will expire in #{days_until_expiration} days.\n Please update your billing information."
    
    # Good
    notice = <<~HEREDOC
      Subscription expiring soon!.
      Your free trial will expire in #{days_until_expiration} days.
      Please update your billing information.
    HEREDOC
    
  • Use format when formatting string to a certain format.

    # Bad
    formatted_card_number = "#{card_brand} ******#{card_last_digits}"
    
    # Good
    CARD_NUMBER_FORMAT = '%{card_brand} ******%{card_last_digits}'.freeze 
    
    formatted_card_number = format(CARD_NUMBER_FORMAT, card_brand: card_brand, card_last_digits: card_last_digits)
    

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
    
  • Use endless method for writing single liner method (This convention only applicable for Ruby 3 or later).

    # Bad
    def square(x) 
      x * x
    end
    
    # Good
    def square(x) = x * x
    

    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