/rails_standards

A developer's guide of practices to follow when building Rails applications.

Rails 4.X Development Standards Guide

Approach

Apply the YAGNI and KISS principles to all of the following.

  • General architecture
  • Product and API features
  • Implementation specifics

Documentation

Make an effort for code to be self documenting.

  • Prefer descriptive names in your code. e.g. user_count is a better name than len.
  • Use YARD formatted comments when code documentation is deemed necessary.
  • Avoid in method comments as they are a cue that the method is too complex; instead, refactor into additional classes/methods that better express intent & purpose.

General Guidelines

These guidelines are based on Sandi Metz's programming "rules" which she introduced on Ruby Rogues.

The rules are purposefully aggressive and are designed to give you pause so your app won't run amok. It's expected that you will break them for pragmatic reasons... just be sure that you aware of the trade-offs.

  • Classes can be no longer than 100 lines of code.
  • Methods can be no longer than 5 lines of code.
  • Methods can take a maximum of 4 parameters.
  • Controllers should only instantiate 1 object.
  • Views should only have access to 1 instance variable.
  • Never directly reference another class/module from within a class. Such references should be passed in.

Be thoughtful when applying these rules. If you find yourself fighting Rails too often, a more pragmatic approach might be warranted.

Models

  • Never use dynamic finders. e.g. find_by_...
  • Apply extreme caution when using callbacks and observers as they typically lead to unwanted coupling.

All models should be organized using the following format.

class MyModel < ActiveRecord::Base
  # extends ...................................................................
  # includes ..................................................................
  # relationships .............................................................
  # validations ...............................................................
  # callbacks .................................................................
  # scopes ....................................................................
  # additional config (i.e. accepts_nested_attribute_for etc...) ..............
  # class methods .............................................................
  # public instance methods ...................................................
  # protected instance methods ................................................
  # private instance methods ..................................................
end

NOTE: The comments listed above should exist in the file to serve as a visual reminder of the format.

Controllers

Controllers should use strong parameters to sanitize params before performing any other logic.

Concerns

Its generally a good idea to isolate concerns into separate modules. Use concerns as outlined in this blog post.

  • Concerns should be created in the app/COMPONENT/concerns directory.
  • Concerns should use the naming convention COMPONENT + BEHAVIOR. For example, app/models/concerns/model_has_status.rb or app/controllers/concerns/controller_supports_cors.rb.
  • CRUD operations that are limited to a single model should be implemented in the model. For example, a full_name method that concats first_name and last_name
  • CRUD operations that reach beyond this model should be implemented as a Concern. For example, a status method that needs to look at a different model to calculate.
  • Simple non-CRUD operations should be implemented as a Concern.
  • Important! Concerns should be isolated and self contained. They should NOT make assumptions about how the receiver is composed at runtime. It's unacceptable for a concern to invoke methods defined in other concerns; however, invoking methods defined in the intended receiver is permissible.

Extensions & Monkey Patches

  • Be thoughtful about monkey patching and look for alternative solutions first.
  • Use an initializer to load extensions & monkey patches.

All extensions & monkey patches should live under an extensions directory in lib.

|-project
  |-app
  |-config
  |-db
  |-lib
    |-extensions <-----

Use modules to extend objects or add monkey patches. This provides some introspection assistance when you need to track down weirdness.

Here's an example:

module CowboyString
  def downcase
    self.upcase
  end
end
::String.send(:include, CowboyString)
String.ancestors # => [String, CowboyString, Enumerable, Comparable, Object, Kernel]

Project Structure

Unfortunately, some of the defaults in Rails seem to encourage monolithic design. This is especially true for developers new to the framework. However, it's important to note that Ruby & Rails include everything you need to create well organized projects.

The key is to keep concerns physically isolated. Applying the right strategies will ensure your project is testable and maintainable well into the future.

Domain Objects

Meaningful projects warrant the creation of domain objects, which can usually be grouped into categories like:

  • policies
  • presenters
  • services
  • etc...

Domain objects should be treated as first class citizens of your Rails application. As such they should live under app/DOMAIN. For example:

  • app/policies
  • app/presenters
  • etc...

Always apply Rails-like standards to domain object names. For example, app/presenters/user_presenter.rb

Additional reading on the subject of domain objects.

Gems & Engines

Sometimes concerns should be grouped into isolated libraries. This creates clear separation & allows strict control of depedencies.

Note: This approach does not require you to open-source parts of the app.

Bundler supports 2 strategies that facilitate this type of application structure.

  1. Locally pathed gems - look for the :path option
  2. Git hosted gems

You can also host a local Gemserver.

It can be daunting to know when creating a gem or engine is appropriate. Stephan Hagemann's presentation at Mountain West Ruby provides some guidance. He is also writing a book on Component based Rails Applications.

Custom gems & engines should be created under the vendor directory within your project.

|-project
  |-vendor
    |-assets
    |-engines <-----
    |-gems    <-----

Additional reading on creating modular Rails applications.

Here are some popular Rails engines that illustrate how to properly isolate responsibilities to achieve modularity.

Refactoring

Good software design often emerges empirically from implementation. The practice of continually moving toward better design is known as refactoring. Plan on a persistent effort to combat code's natural state of entropy. Use prudence to ensure you don't attempt refactoring too much at once.

Refactoring Priorities

  1. Refactor to methods before classes
  2. Refactor to classes before libraries
  3. Refactor to libraries before services
  4. Refactor to libraries and/or services before a rewrite

Gem Dependencies

Gem dependencies should be hardened before deploying the application to production. This will ensure application stability.

We recommend using exact or tilde version specifiers. When using tilde specifiers, be sure to include at least the major & minor numbers. Here's an example.

# Gemfile
gem 'rails', '3.2.11' # GOOD: exact
gem 'pg', '~>0.9'     # GOOD: tilde
gem 'yell', '>=1.2'   # BAD: unspecific
gem 'nokogiri'        # BAD: unversioned

Bundler's Gemfile.lock solves the same problem, but we discovered that inadvertent bundle updates can cause problems. It's much better to be explicit in the Gemfile and guarantee app stability.

This will cause your project to slowy drift away from the bleeding edge. A strategy should be employed to ensure the project doesn't drift too far from contemporary gem versions. For example, upgrade gems on a regular schedule (every 3-4 months) and be vigilant about security patches. Using bundle outdated will help with this.

Code Quality Tools

It's a good idea to run regular code analysis against your project. Here are some of the tools we use.

A Note on Client Side Frameworks

Exciting things are happening in the world of client side frameworks.

Be thoughtful about the decision to use a client side framework. Ask yourself if the complexity of maintaining 2 independent full stacks is the right decision. Often times there are better and simpler solutions.

Read the following articles before deciding. In the end, you should be able to articulate why your decision is the right one.

JavaScript is like a spice. Best used in sprinkles and moderation.

— DHH (@dhh) September 2, 2013
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

We generally agree with DHH regarding client side frameworks and have discovered that thoughtful use of something like Vue meets most of our needs.