Ruby Style Guide

Patterns / Concepts

There are specific architectural patterns and concepts that we utilize within the Lessonly application. Below are page specfic resources detailing when to use and how to implement the respective theory.

Overview

It’s highly recommended you use a Ruby version manager like rbenv, RVM, or chruby so you can try out different versions if necessary.

Regarding coding style, we follow Github’s Ruby Style Guide, which is largely based on the community Ruby Style Guide, edited by Bozhidar Batsov.

Additional House Rules

These override either Github’s or Batsov’s styleguide where applicable:

Use double-quotes whenever possible.

# bad
name = 'foo'

# good
name = "foo"

Close your parens when invoking a method.

# bad
many_things.include? thing

# good
many_things.include?(thing)

Use the Ruby 1.9-style hash syntax with symbol keys wherever possible.

For example: key: 'value'. Only use "hash" => "rocket" syntax when hash keys are required to be strings.

Use a single newline above and below “private” and “protected” in classes.

# bad
cool_hash = { :lol => "nope" }

# good
cool_hash = { lol: "nope" }

# hm okay
cool_hash = { "lol" => "nope" }

Use a single space within ERB tags.

e.g. <%= "foo" %>, not <%="foo"%>

<!-- bad -->
<%="foo"%>

<!-- good -->
<%= "foo" %>

Use full words for variables except where a single letter is conventional.

For examples of where a single letter is conventional, consider f for Rails form objects, i for iterators, etc.

# not so clear
assignee.assignments.map(&:assignable).each { |a| ...

# so clear!
assignee.assignments.map(&:assignable).each { |assignable| ...

Avoid mixing inline and multi-line conditionals: they hurt readability.

# Easy to miss the second condition at the end
if some_condition
  do_something_with_a_really_long_method_name if some_other_condition
end

# It's easier to notice both conditions when they're in the same place
if some_condition && some_other_condition
  do_something_with_a_really_long_method_name
end

Try to use boolean values in conditionals.

…rather than relying on nil to be false and “anything else” to be true. It adds clarity to the purpose of the conditional.

# Uses the presence of an object as a boolean
if current_user
  # Do something
end

# Explicitly gets a boolean value representing the presence of a user
if current_user.present?
  # Do something
end

freeze strings when their values should never change.

…for example, in constants. It will make the string immutable, which has two advantages. It optimizes memory usage (because Ruby points to the same String object instead of instantiating a new String on each reference), and also prevents tempering, because in Ruby, constants aren’t.

DEFAULT_TITLE = "Untitled"
DEFAULT_TITLE << "foo"
DEFAULT_TITLE.inspect # => "Untitledfoo"

DEFAULT_TITLE = "Untitled".freeze
DEFAULT_TITLE << "foo" # => RuntimeError: can't modify frozen string

Use floor instead of to_i when coercing a float to an integer without rounding.

This makes it clear that the intention is specifically to round the number down.

# Unclear: uses to_i to truncate the float
(progress.completed_percent * 100).to_i

# More clear: uses floor to round the float down
(progress.completed_percent * 100).floor

Use leading periods when chaining methods on multiple lines.

Too much method chaining is often a bad code smell that something is responsible for too much at once, but if do you happen to find yourself on the multi-line choo choo method chain train please chain responsibly with leading periods. e.g.

# bad
LatestProgress.
  where(user: manageable_users, lesson: company_lessons).
  completed_yesterday.
  progresses.
  includes(:lesson, user: [:custom_user_fields, :custom_user_field_values]).
  present?

# good
LatestProgress
  .where(user: manageable_users, lesson: company_lessons)
  .completed_yesterday
  .progresses
  .includes(:lesson, user: [:custom_user_fields, :custom_user_field_values])
  .present?

Prefer keyword arguments whenever it isn’t abundantly obvious what an argument is.

# The method name makes it clear what the argument is...
# no need to name the argument.
def assignment_status(assignment)

# Even with multiple arguments, if the method name makes
# the order clear, no need to name the arguments.
def add_user_to_groups(user, groups)

# But when the method name gives little hint of the type of argument,
# specify them by name for maximum clarity.
def notify_of_new_assignments(user:, new_assignments:)

Explicitly declare modules.

# So good
module MyModule
  class MyClass

# No good
class MyModule::MyClass

The latter introduces load order dependencies (MyModule must already be loaded or Ruby will raise a NameError) and requires every reference to other MyModule constants within MyClass to also be fully-qualified.

Gemfile

We try to use pessimistic version constraints for all gems in the Gemfile. These use the ~> operator followed by at least a major and minor gem version (e.g. "~> 4.2"). This prevents a gem from being accidentally upgraded by more than a minor version, and Gems following Semantic Versioning promise to introduce non-backwards-compatible changes only in major versions.

PrivateAttr vs private attr_reader vs instance variables

Currently in the code there are three styles of accessors:

  • Use of PrivateAttr
  • Use of private with accessors below
  • Just using instance variables for information hiding

PrivateAttr is the preferred method for declaring private accessors even though both PrivateAttr and having the attr_reader below private accomplish the same thing. This is for consistency, so everything is defined in one place within the file (at the top). Using instance variables should be avoided.

Some of the differences are described below:

PrivateAttr

The PrivateAttr functionality was added via PR #2787 as a way to limit the public interface and use private methods rather than instance variables. One of the advantages of using PrivateAttr is that all of the instance variables will be listed at the top of the file for clarity.

class MyClass
  extend PrivateAttr

  private_attr_reader :foo, :bar, :baz

  initialize(foo, bar, baz) do
    @foo = foo
    @bar = bar
    @baz = baz
  end

private attr_reader

The same thing can be accomplished by placing the attr_reader below private. One of the disadvantages is that in large files it can be easy to overlook the attr_reader that is somewhere below private in the middle of the code.

class MyClass

  initialize(foo, bar, baz) do
    @foo = foo
    @bar = bar
    @baz = baz
  end

  private

  attr_reader :foo, :bar, :baz

instance variables

Use of instance variables should be avoided.

See the “Writing Code That Embraces Change” section in Chapter 2 of Sandi Metz’s Practical Object Oriented Design in Ruby, for reasons to use private methods rather than instance variables. Specifically, about wrapping all instance variables in reader and/or writer methods with attr_reader, attr_writer, or attr_accessor, and if these methods don’t need to be public then making them private.