Skip to content

Latest commit

 

History

History
556 lines (390 loc) · 12.1 KB

File metadata and controls

556 lines (390 loc) · 12.1 KB

Ruby Style Guide

We've adapted this from GitHub's Ruby style guide. Feel free to make a pull request to add to this guide!

Naming

  • Use snake_case for methods and variables.

  • Use TitleCase for classes and modules.

  • Use SCREAMING_SNAKE_CASE for 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 self or 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!

Spaces

  • 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, and attr_writer:

    module Namespace
      class SomeClass
        include SomeConcerns
    
        def initialize
        end
    
      end
    end
    class TextContainer
      attr_reader :text
    
      def initialize(text)
        @text = text
      end
    
    end

Indentation

  • Indent when as deep as case.

    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/private keywords as deep as class.

    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 def statements.

    class SomeClass
    
      def public_method
        # ...
      end
    
    private
    
      def private_method
        # ...
      end
    
    end
  • rescue blocks should be outdented from the method block

    def 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 }

Parentheses

  • Use def with 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)

Chaff

  • Avoid return where not required. However, a great place to use an explicit return is with an if/unless in 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 self as the recipient of internal class or instance messages unless assigning a value (without self, 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

Strings

  • 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"

Conditionals

  • Never use then for multi-line if/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 over if/then/else/end constructs 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/else constructs 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/unless usage when you have a single-line body.

    # bad
    if some_condition
      do_something
    end
    
    # good
    do_something if some_condition
  • Never use unless with else. 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

Idiomatic Ruby

  • Never use for, unless you know exactly why. Use iterators (e.g. each) instead.

    for is implemented in terms of each (so you're adding a level of indirection), but with a twist - for doesn't introduce a new scope (unlike each) 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 ||, not and and or.

  • 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 %w and %i freely for arrays of short strings and symbols, respectively.

    STATES = %w{ draft open closed }
    KEYS = %i{ here there everywhere }
  • Use x modifier 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.union to 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

Blocks

  • Prefer {...} over do...end for 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...end for 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 }

Dragons

  • Don't use the === (threequals) operator to check types.

    Why? === is mostly an implementation detail to support Ruby features like case, and it's not commutative. For example, String === "hi" is true and "hi" === String is false. Instead, use is_a? or kind_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 than String#+, 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: \A and \z.

    string = "some injection\nusername"
    string[/^username$/]   # matches
    string[/\Ausername\z/] # don't match