timani / ruby-style

My personal Ruby Style Guide along with tools you can use to make your own! See usage.md

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ruby Style Guide

These rules may be broken if it increases code clarity or maintainability

Always default to using the style of the code you are modifying

Design

Classes should do one thing and one thing only

If a class has only one method, consider using a lambda or proc instead

Why? A class with one method, especially Doer.do, is just a function, so make it a lambda

For a base class with abstract methods, include the methods in the base class and have them raise.

Why? This gives you a single place to document which methods subclasses are expected to implement, and ensures that, at least at runtime, they are implemented

Avoid instantiating classes inside other classes. Prefer dependency injection and default parameter values.

Why? This makes it easier to test the classes, but doesn't require Herculean efforts to instantiate classes at runtime:

class PersonFormatter
  def format(person)
    # whatever
  end
end

# Wrong; we are tightly coupled to an implementation and have to use
# crazy mocks to fake this out
class View
  def render
    # code
    puts PersonFormatter.new.format(person)
    # more code
  end


# Correct; Option 1 - constructor injection
class View
  def initialize(person_formatter=PersonFormatter.new)
    @person_formatter = person_formatter
  end

  def render
    # code
    puts @person_formatter.format(person)
    # more code
  end
end

# Correct; Option 2 - setter injection with a sensible default
class View
  attr_writer :person_formatter
  def initialize
    @person_formatter = PersonFormatter.new
  end

  def render
    # code
    puts @person_formatter.format(person)
    # more code
  end
end

Don't make methods public unless they are part of the public interface

Why? Public is the way to formally document the contract of your class. Putting methods that shouldn't be called by others in the public interface is just lazy, and increases maintenance down the road.

protected is likely not correct; only use it for the template method pattern and even then, would a lambda or proc work better?

Why? Protected is used to allow subclasses to call non-public methods. This implies a potentially complex type hierarchy, which is likely not correct for what you are doing.

Avoid the ClassMethods pattern for sharing class methods via a module, since simple use of extend will do

Why? Although it was idiomatic in Rails, the whole idea of overriding included to call extend is a bit overkill. The client code can simply use extend. You could use the InstanceMethods concept to add instance level methods if you like. See this article from Yehuda Katz for more info.

module Helper
  def has_strategy(strat)
    cattr_accessor :strategy
    # or singleton_class.send(:attr_accessor,:strategy) if not in Rails land
    self.strategy = strat
    include InstanceMethods
  end

  module InstanceMethods
    def operate
      case self.class.strategy
      when :foo then puts "FOO!"
      when :bar then puts "BAR!"
      else
        puts "Doing #{self.class.strategy}"
      end
    end
  end
end

class UsesHelper
  extend Helper

  has_strategy :foo
end

UsesHelper.new.operate

Do not use ivars as a way of avoiding method parameters.

Why? Instance variables are a form of global data, and your routines' complexity increases when their input comes from multiple sources. If the instance variables control flow or logic, pass them in as parameters

Private methods should be used to make public methods clear; they should avoid reliance on ivars if at all possible

Why? Private methods that do not rely on instance variables can very easily be extracted to new classes when things get compledx

Private methods calling more private methods might indicate a second class is hiding inside this class

Files

One class/module per file

Why? Makes it simpler to locate and organize code

Follow Rails conventions in Rails apps

For non-Rails apps, code goes in lib, stdlib extensions go in ext

Why? This is where most Rubyists will expect it, and clearly delineates your app from lib extensions

Code for a class/module goes in the lower-cased, snake-cased named file, in a directory structure that matches the module namespaces. For example, the class Foo::Bar::Baz should be in foo/bar/baz.rb, relative to lib or ext

Why? Makes it simpler to locate the code for a given class

Test::Unit files should go in test and be named class_test.rb, e.g. to test the class Foo, create test/foo_test.rb.

Why? Test dir is then sorted by classname, making it easy to visually scan or auto-complete test names

Nest test classes to match modules if you have a large library with a lot of different namespaces

Follow RSpec and Cucumber file/dir naming conventions

Formatting

2-space indent

Why? this is standard across almost all Ruby code

For more than 2 groups of arrowed, equaled, or coloned lines (e.g. a hash or set of local variables), align the operators so that the code forms a 'table'

Why? Although it's extra work to maintain and can break diffs, I feel that the clarity and readability of this structure is worth it - the code is read many more times than changed

Avoid single-line methods

Why? Single-line methods are harder to change when the method needs an additional line

Keep line lengths 80 characters or less unless doing so negatively impacts readability (e.g. for large string literals)

Why? Excessively long lines can be very difficult to read and understand; a low line length encourages keeping things simple

Literal arrays or hashes should have a comma after every element, including the last one

Why? This makes it easier to add elements and reorder elements without having to worry about missing commas

For 'options-hash' calls, align keys across multiple lines, one per line, e.g.

Why? This makes it easier to read and modify the options sent to the method

some_call(non_option1,non_option2,:foo => "bar",
                                  :blah => "quux",
                                  :bar => 42)

When declaring attributes with attr_reader or attr_accessor, put one per line

Why? This makes it easier to add new attributes without creating complex diffs. It also documents what the attributes represent

# Wrong; hard to modify
attr_accessor :first_name, :last_name, :gender

# Right; we can easily modify and document
attr_accessor :first_name
attr_accessor :last_name
attr_accessor :gender

Literal lists should be on one line or one element per line, depending on length

Why? Two-dimensional data is hard to read and modify.

protected and private should be aligned with class

Why? No reason, just my personal preference

class SomeClass

  def public_method
  end

protected

  def protected_method
  end

private

  def private_method
  end
end

protected and private are surrounded by newlines

Why? Visually sets them off

A newline after class or module

Why? Visual clearance

A newline after end unless it's the string of ends for the end of the class/module

Why? I don't get much value from visual clearance for the end blocks that 'close' a class or module

Class methods should be defined via def self.method_name and not inside a class << self

Why? This reduces errors caused by moving methods around, and also doesn't require you to scroll up in the class to determine what type of method it is; you can see it instantly

When using the return value of an if expression, indent the else and end with the if:

Why? This makes it very clear that you are using if..else..end as a complex expression and not as a control structure

# Wrong
result = if something?
  :foo
else
  :bar
end

# Right
result = if something?
            :foo
         else
            :bar
         end

Naming

Variables

Avoid abbreviations

Why? Abbreviations can clash, require explanation and generally disrupt the flow of things

Avoid one-character names

Why? No reason not to be descriptive

For non 'primitive' types, use the name of the class, or the name of the class plural if a collection, unless there will be multiple instances in scope, then do not follow this convention

Why? When there's no particular specific name for something, using its type makes it easy to remember what the object is

# Just use the classname
def routine
  customer = Customer.find(124)
  customers = Customer.find_all_by_gender("M")
end

# Here we have two lists, so neither should just be "customers"
def other_routine
  males = Customer.find_all_by_gender("M")
  minors = Customer.where('age < ?',18)
end

# Here we have a very long method that, even though it should be refactored,
# isn't, so we use a more verbose variable name to keep things clear 50 lines in.
def other_routine
  # tons of code
  male_customers = Customer.find_all_by_gender("M")
  minor_customers = Customer.where('age < ?',18)
  # tons more code
end

For procs and lambdas, use a verb as opposed to foo_proc or foo_block

Why? Procs and lambdas are more like methods and thus should be verbs, since they do something

# Wrong; the variable has been needlessly "nounified" for no real benefit
saver = lambda { |x| x.save! }

# Correct; the variable, being a verb, is instantly recognizable as an action
save = lambda { |x| x.save! }

Methods

Follow the 'referentially transparent' naming scheme of using foo and foo= for 'accessors'.

Why? Since Ruby allows the '=' form, using 'get' or 'set' in the names just adds noise

Boolean methods end in a question mark

Why? This allows methods that can be used in expressions to clearly stand out and makes code more fluent

Dangerous methods end in a bang.

Why? This calls out methods whose use should be carefully understood

# Mutates the object; dangerous on an otherwise immutable object
def process!
  @processed = true
end

# Has a side-effect that is not obvious and might not be idempotent
def render!(output)
  queue_event(:some_event)
  output.puts @content
end

# Raises an exception on failure, unlike its analog, save, which does not
def save!
  raise ValidationException unless self.save
end

Classes And Modules

For non-Rails apps, namespace all classes in a top-level module named for your app or library

Why? Prevents naming clashes when your code is used with other libraries

Class names should be comprehensible without their module namespace.

Why? Ensures that classnames are understood everywhere used.

# Wrong; 'Base' is not an accurate classname
class ActiveRecord::Base
end

# Good; using the class without its namespaced module doesn't remove any clarity
class Gateway::BraintreeGateway
end

Class names should be nouns

Why? Classes represent things, and things are nouns

Non-namespace module names should tend toward adjectives, e.g. Enumerable

Why? Modules used as mixins represent a trait or aspect that you want to use to enhance a class. These are naturally adjectives.

Namespace module names should tend toward nouns

Why? Using modules just for namespacing is, again, specifying things, which are nouns

General Style

Avoid 1.9-style hash syntax

Why? This syntax only works if your hash keys are symbols; otherwise you have to use the old syntax. There's a very limited benefit to the new syntax, and the cost is two ways of doing things, increasing mental overhead when reading/writing code. Further, for libraries, 1.8 compatibility is nice to achieve

For libraries or CLI apps, stick to 1.8-compatible features where possible

Why? Many shops are still on 1.8, and many systems have 1.8 as the default, so there's no reason not to keep 1.8 compatible for code that will be shared

Blocks that return a value you intend to use should use curly braces

Why? This visually sets them off, and makes for a cleaner chaning syntax

# Wrong; calling a method on 'end' is awkward looking
male_teens = Customers.all.select do |customer|
  customer.gender == :male 
end.reject do |man|
  man.age > 19 || man.age < 13
end

# Right; the chaining and use of the comprehensions is clear
male_teens = Customers.all.select { |customer|
  customer.gender == :male 
}.reject { |man|
  man.age > 19 || man.age < 13
}

Blocks that do not return a value, return a value you will ignore, or represent a method definition use do..end

Why? These blocks are more like control structures or methods, so do..end is more natural. It sets them off from blocks that produce a useful value

Always use parens when calling methods with arguments unless you are coding in a heavy 'DSL Style' bit of code

Why? Parens visually set off the parameters, and reduce confusion about how Ruby will parse the line, making the code easier to maintain

# Given this code
class Person
  def self.create(name,status)
    # ..
  end
end
Person.create 'Dave', :single
# and we change it so that :status is based on a condition

# Wrong; what does this line do?
Person.create 'Dave', wife || :single

# If we used parens from the get go, it's a no brainer
Person.create('Dave',:single)
# Right; code is clear
Person.create('Dave',wife || :single)

Do not use else with an unless

Why? the expression becomes too difficult to unwind, just use an if

Do not use an unless for any expression that requires a ||, &&, or !. Either extract to a method or use if

Why? unless is like putting a giant !() around your expression, so it becomes harder and harder to understand what is being tested by adding this. It's not worth it

# Wrong; too hard to figure out
unless person.valid? && !person.from('US')
  # doit
end

# Right; DeMorgan's law simplified this
if !person.valid? || person.from('US')
  # doit
end

# Better; use a method
unless valid_and_foreign?(person)
  # doit
end

Use inline if/unless for 'early-exit' features only

Why? Code can become hard to read when conditions are trailing and complex. Early-exit conditions are generally simple and can benefit from this form

# Wrong; too complex
person.save unless person.from('US') || person.age > 18

# OK; an early exit
raise "person may not be nil" if person.nil?

Do not catch Exception unless you really want to catch 'em all.

Why? Exception is the base of all exceptions, and you typically don't want to catch memory exceptions and the like. Catch StandardError instead

When you must mutate an object before returning it, avoid creating intermediate objects and use tap:

Why? Intermediate objects increase the mental requirements for understanding a routine. tap also creates a nice scope in which the object is being mutated; you will not forget to return the object when you change the code later

# Wrong
def eligible_person(name)
  person = Person.create(:name => name)
  person.update_eligibility(true)
  person.save!
  person
end

# Right
def eligible_person(name)
  Person.create(:name => name).tap { |person|
    person.update_eligibility(true)
    person.save!
  }
end

Feel free to chain Enumerable calls using simple blocks instead of one complex block that does everything.

Why? The braces syntax encourages clean chaning, and with simple blocks, you can easily follow step-by-step what is being done without having to have a bunch of private methods explaining things.

Avoid conditionals based on the truthiness or falsiness of a variable. Use .present? or .nil? to be clear as to what's being tested

Why? It's a lot clearer when you state exactly what you are testing; this makes it easier to change the code later, and avoids sticky issues like 0 and the empty string being truthy

# Wrong; intent is masked
if person.name
  # do it
end

# Right; we can see just what we're testing
if person.name.present?
  # do it
end

If your method returns true or false, be sure those are the only values returned.

Why? Returning a 'truthy' value will lead to unintended consequences, and could lead to complex dependencies in your code. You don't need the hassle

Testing

Unit test methods should have three parts: Given, When, Then

Why? This makes it clear what each section of the test is doing, which is crucial when tests get complex

def test_something
  # Given
  person = Person.new("Dave",38)

  # When
  minor = person.minor?

  # Then
  assert minor,"Expected #{person.inspect} to be a minor"
end

For mocking, include a fourth part before 'When' so that the flow of the test is maintained

Why? It's important to establish what the mock expectations are, and thinking of them as separate from the given helps the test read better.

def test_something
  # Given
  person = Person.new("Dave",38)

  # When the test runs, Then
  AuditStats.expects(:query).returns(true)

  # When
  minor = person.minor?

  # Then
  assert minor,"Expected #{person.inspect} to be a minor"
end

If your only assertion is that mock expectations are met, include a comment indicating this

Why? It's important to let others know your intent that the mock expectations ARE the test and that you just didn't forget an assert

def test_something
  # Given
  person = Person.new("Dave",38)

  # When the test runs, Then
  PersonDAO.expects(:save).with(person)

  # When
  person.save!

  # Then mock expectations should've been met
end

For block-based assertions, place the code inside a lambda to preserve the structure

Why? Again, this preserves the flow of the test

def test_something
  # Given
  name = 'Dave'

  # When
  code = lambda { Person.create(:name => name) }

  # Then
  assert_difference('Person.count',&code)
  assert_equal name,Person.last.name
end

Avoid literals that aren't relevant to the test; abstract into a any method or some method, e.g. some_int

Why? Tests littered with literals can be very hard to follow; if the only literals in the test are those specific to this test, it becomes very clear what's being tested.

For literals that are relevant to the test, do not repeat them in a test

Why? Just like magic strings are bad in real code, they are in test code. Plus it codifies that the values are the same by design and not just by happenstance

Extract setup and assertions that are the same by design into helper methods

Why? If you are running a different test under the exact same conditions as another test, extract that duplicated code into a helper. Similarly, if you are conducting a different test that should have the same outcome, extract those assertions to a helper.

Leave repeated code that is the same by happenstance

Why? This sort of duplication is OK because the code is only the same by happenstance, and may diverge as the code matures. By leaving it seperate, it's easier to change

The 'When' part of your tests should ideally use the public API of the class under test.

Why? Some RSpec constructs assert things about the class under test without calling its public API, e.g. person.should be_valid. This goes against the spirit of TDD, and requires the reader to make a mental transaction between the testing DSL and the class' method, with no discernable benefit.

If you need to sanity-check your setup in the 'Given', use raise instead of assert

Why? raise will mark your test as erroneous, not failed, as that is the case when the setup conditions for the test aren't correct. Better yet, don't use brittle globally-shared test fixtures or factories.

Avoid fixtures, factories, or other globally-shared setup data.

Why? As an app matures, the fixtures or factories become incredibly brittle and hard to modify or understand. It also places key elements of your test setup far away from the test itself, making it hard to understand any given test.

Rails

Controllers

There should be very few if statements; controllers should be as dumb as possible

Why? if statements usually imply business logic, which does not belong in controllers. The logic in the controller should be mainly concerned with sending the correct response to the user.

Avoid excessive filters, or filters that are highly conditional

Why? When the number of filters increases, it becomes harder and harder to know what code is executing and in what order. Further, when filters set instance variables, it becomes increasingly difficult to understand where those variables are being set, and the filters become very order-specific. Finally, conditional filters, or filters used on only one controller method increase complexity beyond the point of utility

rake routes should be the truth, the whole truth, and nothing but the truth

Why? By lazily creating all routes for a resource, when you only need a few to be valid, you create misleading output for newcomers, and allow valid named route methods to be created that can only fail at runtime or in production.

# Wrong; our app only supports create and show
resources :transactions

# Right; rake routes reflects the reality of our app now
resources :transactions, :only => [:create, :show]

Prefer exposing the exact objects views require rather than 'root' objects requiring deep traveral

Why? When views navigate deep into object hierarchies, it becomes very difficult to understand what data the views really do require, and it makes refactoring anything in those object hierarchies incredibly difficult

# A view for a person's credit cards requires the person's name, and a list of last-4, type, and expiration date of cards

# Wrong; the view must navigate through the person to get his credit cards and has
# access to the entire person object, which is not needed
def show
  @person = Person.find(params[:person_id])
end

# Wrong; although the view can now access credit cards directly, it's still not clear what data
# is really needed by the view
def show
  @person = Person.find(params[:person_id])
  @credit_cards = @person.credit_cards
end

# Right; the ivars represent what the view needs AND contain only what the view needs.
# You may wish to use a more sophisticated "presenter" pattern instead of OpenStruct
def show
  @person_name = Person.find(params[:person_id]).full_name
  @credit_cards = @person.credit_cards.map { |card|
    OpenStruct.new(:last_four => card.last_four, 
                   :card_type => card.card_type,
                   :expiration_date => [card.expiration_month,card.expiration_year].join('/'))
  }
end

Do not create ivars unless they are to be shared with the views.

Why? Controllers are special types of classes. You don't instantiate them, and the don't hold state. ivars in a controller are only for passing data to a view. If you need to pass data to prive methods, use method arguments.

Active Record

Avoid hooks

Why? Hooks make your models very hard to use in different ways, and lock them to business rules that are likely not all that hard and fast. They also make testing very difficult, as it becomes harder and harder to set up the correct state using objects that have excessive hooks on them.

# Wrong; we've hidden business logic behind a simple CRUD operation
class Person < ActiveRecord::Base
  after_save :update_facebook

private

  def update_facebook
    # send Facebook some status update 
  end
end

# Better; we have a method that says what it does
class Person < ActiveRecord::Base

  def save_and_update_facebook
    if save
      # Send Facebook some status update
    end
  end
end

Conditional validations should be used with care

Why? Validations that are not always applicable make it very hard to modify objects and enhance them, because it becomes increasingly difficult to understand what a valid object really is. Further, it becomes very difficult to set up objects in a particular state for a given test if there are a lot of conditonal validations

Use database constraints to enforce valid data in the database

Why? The database is the only place that can truly ensure various constraints, such as uniqueness. Constraints are incredibly useful for making sure that, regardless of bugs in your code, your data will be clean.

AR objects should be as dumb as possible; only derived values should be new methods

Why? It may be tempting to add business logic to your models, but this violates the single responsibility principle, and makes the classes harder and harder to understand, test, and modify. Treat your models as dumb structs with persistence, and put all other concerns on other classes. Do not just mix in a bunch of modules.

About

My personal Ruby Style Guide along with tools you can use to make your own! See usage.md

License:Apache License 2.0