Ruby Web Development

Write beautiful code and move fast without breaking stuff

Yet Another Ruby Style Guide

Beautiful code invites a programmer to learn from it, maintain it, and extend it. Ugly code begs to be thrown out and re-written. Consistent code is more beautiful and easier to read that inconsistent code. Adopting a consistent style will make code more beautiful, and increase its lifespan and impact. This post explains the Ruby style that I have developed from my experience writing Ruby, JavaScript, Java, Python, C++, and Objective C.

My style mostly agrees with Chris Neukirchen’s. Read his if you’re looking for a more condensed summary. Some rules are inspired from Google’s Ruby style guide, which will most likely never be released.

Basic Formatting

80 characters per line. UI studies suggest that people are most efficient at reading text that is 72 characters / line. The limit of 80 accounts for the fact that source code has more punctuation than English text. The limit also allows fitting two code windows side-by-side on a laptop (code / tests), or three windows on a desktop (HTML / CSS / JS).

2-space indentation. Larger indentation might make code easier to read, but definitely makes it harder to fit a decent amount of code on 80-character lines.

No tabs. Editors and browsers may assign different widths to tabs. Decent editors have an option named along the lines of “Use spaces for tabs” that will do the magic for you.

Avoid trailing whitespace, but don’t go out of your way to remove it. Eclipse-based IDEs trash up the code with trailing whitespace. Write your code as cleanly as possible. Don’t submit pull requests consisting solely of removing trailing whitespace, but feel free to piggyback whitespace removal to other stuff.

Blank line between top-level class, module, and/or method definitions. Top-level definitions serve as sections in a source file. Spacing helps visually break down the file into sections.

Examples of Basic Formatting Rules
1
2
3
4
5
6
7
8
9
# Blob (file) in a git repository hosted on this server.
class Blob < ActiveRecord::Base
  # The call below is broken into 2 lines to meet the 80 characters/line limit. 
  validates :size, :presence => true,
      :numericality => { :integer_only => true, :greater_than_or_equal_to => 0 }
end

# One blank line between class definitions.
class BlobsController < ApplicationController

Spacing

Prefer 1 space around operators and keywords. This makes the code easy to read and matches the convention used in all mainstream languages, including C, JavaScript, and Python. Examples: a + b, a ? b : c, while a < b. Exceptions to this rule follow below.

No spaces around the . method dispatcher. Readable and matches other languages. Example: receiver.message.

No spaces between unary operators and their operands. Readable and matches other languages. Examples: !a, -5, ~1.

No spaces after ( and [. No spaces before ] and ). Readable and matches other languages. Examples: to_s(1), array[i].

No spaces between a block’s argument list and the |s wrapping the list. Examples: map { |i| i * 2 }, each_with_index do |e, i|.

1 space between # and comment text. No spaces make the comment hard to read. More spaces waste characters.

2 spaces between code and # comment on the same line. One space makes it easy to confuse the # with an operator like + / *. More spaces would waste characters.

No space before the beginning of the line. Mentioned for completeness, as this should be common sense. Extra spaces would ruin the indentation and make the code hard to read, asides from eating up precious screen estate.

Examples of Spacing Rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Spaces around the < operator.
class BlobsController < ApplicationController
  def hash
    # Special rules for [], (), and unary operators.
    @@hashes[-3 * (super + { hash: 'example' }.hash)]
  end

  def show
    # 1 space around keywords like "do".
    respond_to do |format|
      # No space around ".";  2 spaces between code a comment on the same line.      
      format.html  # show.html.erb
    end
  end
end

Comments

#-comments. =begin and =end are only suitable for a top-level comment providing an overview of the file and/or class. They’re harder to visually separate from source code than #-prefixed comment lines.

Each class and method must have a doc comment. Methods without specifications are unsightly in documentation. For internal methods, or overrides of well-known methods, such as to_s, use YARD’s @see, or RDoc’s :nodoc:.

YARD tags with RDoc Markup by default. Use YARD syntax for new projects. Whenever applicable, use RDOC markup on the YARD-annotated code. When modifying old code, either overhaul the entire project, or use the existing conventions for .

Doc comments start with a one-line summary. Short summaries make doc pages pretty and play well with code-completion UIs.

Blank line between summary and the rest of a doc comment. This lets the summary stand out, so readers can skip over the details if they desire.

Comments before the code they apply to. Really small comments can fit on the same line as the code they refer to. All other comments should be placed on their own line(s), before the code they’re referring to, because that’s where programmers and documentation tools expect them to be.

No blank lines between comments and the code they apply to. This way, the comments are visually associated with the code that they apply to. Furthermore, some documentation tools ignore doc-comments if there’s a space between them and their subject class / module / method.

Prefer full sentences in comments. Comments should read like the code’s author thinking out loud in English, and un-terminated thoughts are bad.

Comment a class or module’s end line with the class / module’s fully-qualified name. Specify if it’s a class, bona-fide module, or namespace. This is especially helpful for long / nested modules and classes. It’s hard to know in advance which parts of your code will grow, so tag all your ends. A “bona-fide” module contains instance methods, and is meant to be included in other classes. A namespace is a module that is solely intended as a container for inner classes and/or class methods.

Examples of Commenting Rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Horrible namespace name.
module Windowing

# Brief comment of what a Widget does.
class Widget
  # Searches all the on-screen Widgets for a name match.
  #
  # @param [String, Regexp] name_pattern used with `=~` against Widget names
  # @return [Array<Widget>] widgets whose name matches the pattern
  def self.matching(name_pattern)
    # Code here.
  end

  # @see Object#to_s
  def to_s
    "#<Widget #{name}>"
  end
end  # class Namespace::Widget

end  # namespace Windowing

Parentheses

No parentheses around clauses for if, while, case, when, etc. This is Ruby, not C or Java. Parentheses make the code more crowded than white-space would.

No empty parentheses after def with no arguments. The def keyword and source file layout make it obvious that a function is being defined.

Parentheses after def with arguments. Normally, we’d prefer as few parentheses as possible. However, this exception is makes it easier to distinguish between method definitions and calls.

No parentheses in method calls without arguments. Other languages recommend or need () to distinguish between instance variable accesses and method calls. Ruby has the @ prefix for instance variables.

No parentheses in outermost method calls, when possible. This makes the code read more like English text. You’ll need parentheses for call chaining, or if you use {}-blocks, so Ruby can parse your code correctly.

Parentheses in inner calls with arguments. Parentheses are unsightly, but necessary to avoid ambiguity. For example, foo bar baz can either mean foo(bar(baz)) or (foo(bar))(baz). Treat keywords like return, next and yield as method calls.

Examples of Parentheses Rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# For brevity, this class example misses doc comments.
class Parens
  # No parentheses when defining methods without arguments.
  def argumentless_method
    # Treat return like a method call.
    return Array.new(5) unless slow_path?
    # The parentheses are necessary because { } would bind to "base" otherwise.
    elements.inject(base) { |acc, e| [acc, e] }
  end

  # Parentheses when defining methods with arguments.
  def method_with_args(arg1, arg2)
    while arg1 < arg2
      # No parentheses for outer call. Parentheses for inner call.   
      arg1 += prime_number_after square_number_after(arg1)

      # Treat yield like a method call. 
      yield arg1
      yield square_number_after(arg1)
    end
  end
end  # class Parens

Line Breaks

Avoid line breaks. Long expressions are harder to reason about. Extracting parts of a complicated expression into named local variables will usually help you reduce the expression’s length. The local variable names also serve as documentation, and might help you write fewer comments.

80-characters per line. Obey the line limit whenever the language allows you to. The best way to observe the limit is to enable the “show print margin” setting in your text editor. This will draw a line at the 80-character mark, and you will be bothered if your code crosses it.

Don’t break long URLs. Long URLs are the main exception to the line limit. If you must include a long URL, try to have a line break right before it, to minimize the scrolling required to read your code.

No \ at the end of a line. If a line ends in an incomplete expression, Ruby will recognize that as a sign that the code continues on the next line. Placing your line breaks right after operators, like +, or right after , (commas), makes it obvious that a line isn’t terminated.

4-space hanging indentation. The 4-space indentation contrasts with the 2-space indentation for regular code lines. This makes it easy to distinguish between stand-alone lines of code, and lines that depend on previous lines.

Expression-aware hanging indentation. You may choose to align your line continuations with the beginning of an open expression, or with the first call of a call chain. Do that when it enhances readability. Don’t do it if it would cause even more line breaks.

No hanging indentation for English text in comments. Follow the convention for typesetting English text when writing English text.

Examples of Line Breaks
1
2
3
4
5
6
7
8
9
10
11
12
# Line breaks after comma, hanging indentation aligns with the open expression.
def method_name(long_argument_name, another_long_argument_name,
                yet_another_long_argument_name)
  # 4-space hanging indentation. It would be impossible to align the second line
  # to the open argument list.
  yet_another_long_argument_name.call_some_verbose_method long_argument_name,
       another_long_argument_name
  # Hanging indentation aligns with the first call in a call chain that
  # continues on the next line.
  long_argument_name.each { |e| e.verbose_method_name }.reject(&:nil?).
                     join(
end

Naming

snake_case for local variables, arguments, methods, instance / class variables, and hash keys / named arguments. This matches most Ruby conventions and existing code.

CamelCase for class and module names. Do not keep acronyms like HTTP and URL capitalized. HttpCache is easier to read and type than HTTPCache.

SCREAMING_CAPS for constant names. This makes it easy to distinguish constants from classes.

Use other to name the argument of a binary operator. This reads well and is consistent with other Ruby code.

Use acc, e or a, e to name the arguments to an inject block. Think “accumulator, element”. Use acc whenever possible, revert to a if line width / code size concerns warrant it. Bonus points if you come up with names that are more meaningful in your code’s specific context.

Examples of Naming Rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Contrived example to showcase correct naming.
class HttpCache
  MAX_PAGES = 100

  # The names here are unnecessarily long, to show the naming convention.
  def prefetch_starting_at(page_url)
    resource_urls = parse_html(raw_string: get(page_url)).links.
        map { |link| link.href }
    resource_urls.each do { |resource_url| get resource_url }
  end

  # New cache containing
  def +(other)
    # "page" is a better name for "e" in this specific context.
    new_pages = other.pages.inject pages.dup do |acc, page|
      # This method body is contrived to show naming. This "inject" call can be
      # replaced by a "concat", for shorter and more readable code. In fact,
      # many "inject" calls are show-offs and can be replaced.
      next if acc.include? page
      acc.push page
      acc
    end
  end
end

Blocks

Use {} for single-line blocks. The notation is more compact. Even if you have to add parentheses around a function call, it’s still shorter than do;; end.

Prefer doend for multi-line blocks. This matches most Ruby code, and do behaves less surprisingly than {. For example, a.inject b { } parses as a.inject(this.b() { }), whereas a.inject b do parses as a.inject(b) do.

Use {} for multi-line blocks with call chaining. Ruby supports call chaining on doend blocks, but it looks weird.

Prefer lambda over proc and Proc.new. It’s shorter, used in other Ruby code, and understandable to most Computer Science majors.

Examples of Block Formatting Rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# One-line block.
Dir['*.rb'].each { |file| File.cp file, "#{file}.bak" }

# Multi-line block with call chaining.
Dir['*.rb'].select { |file|
  File.file?(file) && File.stat(file).size > 0
}.join(', ')

# Lambda. Multi-line block, with no chaining.
def counter(initial_value = 0)
  value = initial_value
  lambda do |method = :value|
    case method
    when :value
      value
    else
      value += 1
    end
  end
end

Language Constructs

Avoid expressions starting with ! (not). In many cases, negative expressions can be replaced with positive expressions, in conjunction with replacing if with unless, while with until, and select with reject.

No for. The each + block syntax makes for more idiomatic Ruby than forin, which looks like JavaScript and Python. Some Rails generators produce code with forin; change the code and, whenever possible, upgrade the generators.

No then. The then keyword is not necessary if the if expression is specified on the next line after the clause. For one-line if expressions, use the ?: ternary operator.

No return on the last line of a method. Ruby methods automatically return the result of the last expression in a method.

Avoid self. method calls. Method calls without a receiver are automatically performed on self. Use self. if Ruby would otherwise confuse a writer method call and a local variable, for example in self.name = "Value".

Use a ||= b for default values. It’s a Ruby idiom for assigning b to a if a is nil. It’s also shorter than var = default unless var or other equivalents.

Use a && a.b for nil handling. Also a Ruby idom, useful for calling b on a only if a is not nil. It is more compact than an ifelse expression.

Prefer alias_method over alias. alias_method requires symbols, which makes it obvious that no method call is being performed.

Built-in Methods

Prefer named methods to indexing and slicing. array.first reads better than array[0], and array.last reads better than array[-1]. However, don’t be afraid to use a clever slice to avoid a method chain.

Prefer map over collect, find over detect, length over size. map is inherited from LISP, exists in JavaScript and Python, and is known to most Computer Science majors. length exists in JavaScript, and is similar to len in Python and to strlen in C.

Source Encoding

UNIX newlines. You don’t need to worry about this unless you’re on Windows. If you’re on Windows, configure your editor and/or git client to use UNIX style (also known as LF or \n) line endings.

7-bit ASCII for code. Stick to the 7-bit ASCII character set for everything, unless you need to use non-English language in UI strings.

No magic encoding comment for ASCII files. If your source code only uses ASCII, don’t add a magic encoding comment to your files. Remember that Ruby’s default encoding is most likely UTF-8, so don’t assume that your strings will be encoded using ASCII-8BIT.

7-bit ASCII Source Code
1
puts 'Hello world'

Use UTF-8 if 7-bit ASCII doesn’t work. If you must use characters outside 7-bit ASCII in UI strings, use UTF-8. Most software that your code might need to interface with (e.g. browsers, database servers) uses UTF-8 by default.

Magic encoding comment for UTF-8 files. If your source code has non-ASCII characters, always add a magic encoding comment to it. Remember that the magic comment must be on the first line of the file. If the first line is a shebang line, the magic comment must immediately follow it. Always leave a blank line after the magic comment.

UTF-8 Source Code
1
2
3
# Encoding: UTF-8

puts '你好'
UTF-8 Source Code with Shebang Line
1
2
3
4
#!/usr/bin/env ruby
# Encoding: UTF-8

puts '你好'

In very rare cases (stubbing low-level functions such as Socket.recv), it might be convenient to set the source encoding to ASCII-8BIT (a.k.a. BINARY). Only do this in test code. Prefer calling force_encoding on Strings, and only use an encoding magic if the test case would be littered with force_encoding calls.

Comments