Coder Social home page Coder Social logo

ruby-style-guide's Introduction

Prelude

Style is what separates the good from the great.
-- Bozhidar Batsov

This document is a fork of what seems to be a standard ruby and rails style guide in the community, modified for how things are done at TeamPages.com.

The Ruby Style Guide

This Ruby style guide recommends best practices so that real-world Ruby programmers can write code that can be maintained by other real-world Ruby programmers. A style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all โ€“ no matter how good it is.

Table of Contents

Source Code Layout

Nearly everybody is convinced that every style but their own is ugly and unreadable. Leave out the "but their own" and they're probably right...
-- Jerry Coffin (on indentation)

  • Use UTF-8 as the source file encoding.

  • Use two spaces per indentation level.

    # good
    def some_method
      do_something
    end
    
    # bad - four spaces
    def some_method
        do_something
    end
  • Use Unix-style line endings. (*BSD/Solaris/Linux/OSX users are covered by default, Windows users have to be extra careful.)

    • If you're using Git you might want to add the following configuration setting to protect your project from Windows line endings creeping in:

      $ git config --global core.autocrlf true

  • Use spaces around operators, after commas, colons and semicolons, around { and before }. Whitespace might be (mostly) irrelevant to the Ruby interpreter, but its proper use is the key to writing easily readable code.

    sum = 1 + 2
    a, b = 1, 2
    1 > 2 ? true : false; puts 'Hi'
    [1, 2, 3].each { |e| puts e }

    The only exception is when using the exponent operator:

    # bad
    e = M * c ** 2
    
    # good
    e = M * c**2
  • Spaces should be used after (, [ and before ], ).

    some( arg ).other
    [ 1, 2, 3 ].length
    def( blah = nil )
    end
  • 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
    
    kind = case year
           when 1850..1889 then 'Blues'
           when 1890..1909 then 'Ragtime'
           when 1910..1929 then 'New Orleans Jazz'
           when 1930..1939 then 'Swing'
           when 1940..1950 then 'Bebop'
           else 'Jazz'
           end
  • Use empty lines between defs and to break up a method into logical paragraphs.

    def some_method
      data = initialize(options)
    
      data.manipulate!
    
      data.result
    end
    
    def some_method
      result
    end
  • Add comment lines above all method declarations

    # -------------------------------------------------------------- some_method
    def some_method
    end
    # ------------------------------------------------------------- other_method
    def other_method( a, b, c )
    end
  • Align the parameters of a method call if they span over multiple lines.

    # starting point (line is too long)
    def send_mail( source )
      Mailer.deliver( to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text )
    end
    
    # bad (normal indent)
    def send_mail( source )
      Mailer.deliver(
        to: '[email protected]',
        from: '[email protected]',
        subject: 'Important message',
        body: source.text )
    end
    
    # bad (double indent)
    def send_mail( source )
      Mailer.deliver(
          to: '[email protected]',
          from: '[email protected]',
          subject: 'Important message',
          body: source.text )
    end
    
    # good
    def send_mail( source )
      Mailer.deliver( to: '[email protected]',
                      from: '[email protected]',
                      subject: 'Important message',
                      body: source.text )
    end
  • Use RDoc and its conventions for API documentation. Don't put an empty line between the comment block and the def.

  • Keep lines fewer than 80 characters.

  • Avoid trailing whitespace.

Syntax

  • Use def with parentheses when there are arguments. Omit the parentheses when the method doesn't accept any arguments.

    def some_method
      # body omitted
    end
    
    def some_method_with_arguments( arg1, arg2 )
      # body omitted
    end
  • Never use for, unless you know exactly why. Most of the time iterators should be used 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.

    arr = [ 1, 2, 3 ]
    
    # bad
    for elem in arr do
      puts elem
    end
    
    # good
    arr.each { |elem| puts elem }
  • Never use then for multi-line if/unless.

    # bad
    if some_condition then
      # body omitted
    end
    
    # good
    if some_condition
      # body omitted
    end
  • Favor the ternary operator(?:) over short if/then/else/end constructs. Do not use single line if bool then ... else ... end statements. Trailing ifs are acceptable for flow coontrol.

    # bad
    result = if some_condition then something else something_else end
    
    # good
    result = some_condition ? something : something_else
    
    # good
    next if condition_true
  • 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
  • Never use if x: ... - it is removed in Ruby 1.9. Use the ternary operator instead.

    # bad
    result = if some_condition: something else something_else end
    
    # good
    result = some_condition ? something : something_else
  • Never use if x; .... Use the ternary operator instead.

  • Use when x then ... for one-line cases. The alternative syntax when x: ... is removed in Ruby 1.9.

  • Never use when x; .... See the previous rule.

  • Use &&/|| for boolean expressions, and/or for control flow. (Rule of thumb: If you have to use outer parentheses, you are using the wrong operators.)

    # boolean expression
    if some_condition && some_other_condition
      do_something
    end
    
    # control flow
    document.saved? or document.save!
  • Avoid multi-line ?: (the ternary operator), use if/unless instead.

  • Favor modifier if/unless usage when you have a single-line body. Another good alternative is the usage of control flow and/or.

    # bad
    if some_condition
      do_something
    end
    
    # good
    do_something if some_condition
    
    # another good option
    some_condition and do_something
  • Favor if over unless at all times.

    # bad
    do_something unless some_condition
    
    # good
    do_something if !some_condition
    
    # another good option
    some_condition or do_something
  • 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
  • Don't use parentheses around the condition of an if/unless/while, unless the condition contains an assignment (see "Using the return value of =" below).

    # bad
    if ( x > 10 )
      # body omitted
    end
    
    # good
    if x > 10
      # body omitted
    end
    
    # ok
    if ( x = self.next_value )
      # body omitted
    end
  • Favor modifier while/until usage when you have a single-line body.

    # bad
    while some_condition
      do_something
    end
    
    # good
    do_something while some_condition
  • Favor until over while for negative conditions.

    # bad
    do_something while !some_condition
    
    # good
    do_something until some_condition
  • Omit parentheses around parameters for methods that are part of an internal DSL (e.g. Rake, Rails, RSpec), methods that are with "keyword" status in Ruby (e.g. attr_reader, puts) and attribute access methods. Use parentheses around the arguments of all other method invocations.

    class Person
      attr_reader :name, :age
    
      # omitted
    end
    
    temperance = Person.new( 'Temperance', 30 )
    temperance.name
    
    puts temperance.age
    
    x = Math.sin( y )
    array.delete( e )
  • Prefer {...} over do...end for single-line blocks. Avoid using {...} for multi-line blocks (multiline chaining is always ugly). Always use do...end for "control flow" and "method definitions" (e.g. in Rakefiles and certain DSLs). Avoid do...end when chaining.

    names = ['Bozhidar', 'Steve', 'Sarah']
    
    # good
    names.each { |name| puts name }
    
    # bad
    names.each do |name|
      puts name
    end
    
    # good
    names.select { |name| name.start_with?('S') }.map { |name| name.upcase }
    
    # bad
    names.select do |name|
      name.start_with?('S')
    end.map { |name| name.upcase }

    Some will argue that multiline chaining would look OK with the use of {...}, but they should ask themselves - it this code really readable and can't the blocks contents be extracted into nifty methods.

  • Avoid return where not required.

    # bad
    def some_method(some_arr)
      return some_arr.size
    end
    
    # good
    def some_method(some_arr)
      some_arr.size
    end
  • Avoid self where not required.

    # bad
    def ready?
      if self.last_reviewed_at > self.last_updated_at
        self.worker.update(self.content, self.options)
        self.status = :in_progress
      end
      self.status == :verified
    end
    
    # good
    def ready?
      if last_reviewed_at > last_updated_at
        worker.update(content, options)
        self.status = :in_progress
      end
      status == :verified
    end
  • As a corollary, avoid shadowing methods with local variables unless they are both equivalent

    class Foo
      attr_accessor :options
    
      # ok
      def initialize(options)
        self.options = options
        # both options and self.options are equivalent here
      end
    
      # bad
      def do_something(options = {})
        unless options[:when] == :later
          output(self.options[:message])
        end
      end
    
      # good
      def do_something(params = {})
        unless params[:when] == :later
          output(options[:message])
        end
      end
    end
  • Use 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

    While several Ruby books suggest the first style, the second is much more prominent in practice (and arguably a bit more readable).

  • Avoid line continuation (\) where not required. In practice, avoid using line continuations at all.

    # bad
    result = 1 - \
             2
    
    # good (but still ugly as hell)
    result = 1 \
             - 2
  • Using the return value of = (an assignment) is ok, but surround the assignment with parenthesis.

    # good - shows intended use of assignment
    if ( v = array.grep( /foo/ ) ) ...
    
    # bad
    if v = array.grep(/foo/) ...
    
    # also good - shows intended use of assignment and has correct precedence.
    if (v = self.next_value) == 'hello' ...
  • Use ||= freely to initialize variables.

    # set name to Bozhidar, only if it's nil or false
    name ||= 'Bozhidar'
  • Don't use ||= to initialize boolean variables. (Consider what would happen if the current value happened to be false.)

    # bad - would set enabled to true even if it was false
    enabled ||= true
    
    # good
    enabled = true if enabled.nil?
  • Avoid using Perl-style special variables (like $0-9, `$``, etc. ). They are quite cryptic and their use in anything but one-liner scripts is discouraged.

  • Never put a space between a method name and the opening parenthesis.

    # bad
    f (3 + 2) + 1
    
    # good
    f( 3 + 2 ) + 1
  • If the first argument to a method begins with an open parenthesis, always use parentheses in the method invocation. For example, write f((3 + 2) + 1).

  • Always run the Ruby interpreter with the -w option so it will warn you if you forget either of the rules above!

  • When the keys of your hash are symbols use the Ruby 1.9 hash literal syntax.

    # bad
    hash = { :one => 1, :two => 2 }
    
    # good
    hash = { one: 1, two: 2 }
  • Use the new lambda literal syntax.

    # bad
    lambda = lambda { |a, b| a + b }
    lambda.call(1, 2)
    
    # good
    lambda = ->(a, b) { a + b }
    lambda.(1, 2)
  • Use _ for unused block parameters.

    # bad
    result = hash.map { |k, v| v + 1 }
    
    # good
    result = hash.map { |_, v| v + 1 }

Naming

The only real difficulties in programming are cache invalidation and naming things.
-- Phil Karlton

  • Use snake_case for methods and variables.

  • Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)

  • 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! (doesn't run the finalizers like exit does), etc.) should end with an exclamation mark if there exists a safe version of that dangerous method.

    # bad - there is not matching 'safe' method
    class Person
      def update!
      end
    end
    
    # good
    class Person
      def update
      end
    end
    
    # good
    class Person
      def update!
      end
    
      def update
      end
    end
  • Define the non-bang (safe) method in terms of the bang (dangerous) one if possible.

    class Array
      def flatten_once!
        res = []
    
        each do |e|
          [*e].each { |f| res << f }
        end
    
        replace(res)
      end
    
      def flatten_once
        dup.flatten_once!
      end
    end
  • When using reduce with short blocks, name the arguments |a, e| (accumulator, element).

  • When defining binary operators, name the argument other.

    def +(other)
      # body omitted
    end
  • Prefer map over collect, find over detect, select over find_all, reduce over inject and size over length. This is not a hard requirement; if the use of the alias enhances readability, it's ok to use it. The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use of select is encouraged over find_all is that it goes together nicely with reject and its name is pretty self-explanatory.

Comments

Good code is its own best documentation. As you're about to add a comment, ask yourself, "How can I improve the code so that this comment isn't needed?" Improve the code and then document it to make it even clearer.
-- Steve McConnell

  • Write self-documenting code and ignore the rest of this section. Seriously!

  • Comments longer than a word are capitalized and use punctuation. Use one space after periods.

  • Avoid superfluous comments.

    # bad
    counter += 1 # increments counter by one
  • Keep existing comments up-to-date. An outdated is worse than no comment at all.

Good code is like a good joke - it needs no explanation.
-- Russ Olsen

  • Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. (Do or do not - there is no try. --Yoda)

Annotations

  • Annotations should usually be written on the line immediately above the relevant code.

  • The annotation keyword is followed by a colon and a space, then a note describing the problem.

  • If multiple lines are required to describe the problem, subsequent lines should be indented two spaces after the #.

    def bar
      # FIXME: This has crashed occasionally since v3.2.1. It may
      #   be related to the BarBazUtil upgrade.
      baz(:quux)
    end
  • In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule.

    def bar
      sleep 100 # OPTIMIZE
    end
  • Use TODO to note missing features or functionality that should be added at a later date.

  • Use FIXME to note broken code that needs to be fixed.

  • Use OPTIMIZE to note slow or inefficient code that may cause performance problems.

  • Use HACK to note code smells where questionable coding practices were used and should be refactored away.

  • Use REVIEW to note anything that should be looked at to confirm it is working as intended. For example: REVIEW: Are we sure this is how the client does X currently?

  • Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's README or similar.

Classes

  • When designing class hierarchies make sure that they conform to the Liskov Substitution Principle.

  • Try to make your classes as [SOLID](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design\)) as possible.

  • Always supply a proper to_s method for classes that represent domain objects.

    class Person
      attr_reader :first_name, :last_name
    
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    
      def to_s
        "#@first_name #@last_name"
      end
    end
  • Use the attr family of functions to define trivial accessors or mutators.

    # bad
    class Person
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    
      def first_name
        @first_name
      end
    
      def last_name
        @last_name
      end
    end
    
    # good
    class Person
      attr_reader :first_name, :last_name
    
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    end
  • Consider using Struct.new, which defines the trivial accessors, constructor and comparison operators for you.

    # good
    class Person
      attr_reader :first_name, :last_name
    
      def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
      end
    end
    
    # better
    class Person < Struct.new (:first_name, :last_name)
    end
  • Consider adding factory methods to provide additional sensible ways to create instances of a particular class.

    class Person
      def self.create(options_hash)
        # body omitted
      end
    end
  • Prefer duck-typing over inheritance.

    # bad
    class Animal
      # abstract method
      def speak
      end
    end
    
    # extend superclass
    class Duck < Animal
      def speak
        puts 'Quack! Quack'
      end
    end
    
    # extend superclass
    class Dog < Animal
      def speak
        puts 'Bau! Bau!'
      end
    end
    
    # good
    class Duck
      def speak
        puts 'Quack! Quack'
      end
    end
    
    class Dog
      def speak
        puts 'Bau! Bau!'
      end
    end
  • Avoid the usage of class (@@) variables due to their "nasty" behavior in inheritance.

    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"

    As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.

  • Assign proper visibility levels to methods (private, protected) in accordance with their intended usage. Don't go off leaving everything public (which is the default). After all we're coding in Ruby now, not in Python.

  • Indent the public, protected, and private methods as much the method definitions they apply to. Leave one blank line above them.

    class SomeClass
      def public_method
        # ...
      end
    
      private
      def private_method
        # ...
      end
    end
  • Use def self.method to define singleton methods. This makes the methods more resistant to refactoring changes.

    class TestClass
      # bad
      def TestClass.some_method
        # body omitted
      end
    
      # good
      def self.some_other_method
        # body omitted
      end
    
      # Also possible and convenient when you
      # have to define many singleton methods.
      class << self
        def first_method
          # body omitted
        end
    
        def second_method_etc
          # body omitted
        end
      end
    end

Exceptions

  • Signal exceptions using the fail keyword. Use raise only when catching an exception and re-raising it (because here you're not failing, but explicitly and purposefully raising an exception).

    begin
      fail 'Oops';
    rescue => error
      raise if error.message != 'Oops'
    end
  • Never return form an ensure block. If you explicitly return from a method inside an ensure block, the return will take precedence over any exception being raised, and the method will return as if no exception had been raised at all. In effect, the exception will be silently thrown away.

    def foo
      begin
        fail
      ensure
        return 'very bad idea'
      end
    end
  • Use implicit begin blocks when possible.

    # bad
    def foo
      begin
        # main logic goes here
      rescue
        # failure handling goes here
      end
    end
    
    # good
    def foo
      # main logic goes here
    rescue
      # failure handling goes here
    end
  • Mitigate the proliferation of begin blocks via the use of contingency methods (a term coined by Avdi Grimm).

    # bad
    begin
      something_that_might_fail
    rescue IOError
      # handle IOError
    end
    
    begin
      something_else_that_might_fail
    rescue IOError
      # handle IOError
    end
    
    # good
    def with_io_error_handling
       yield
    rescue
      # handle IOError
    end
    
    with_io_error_handling { something_that_might_fail }
    
    with_io_error_handling { something_else_that_might_fail }
  • Don't suppress exceptions.

    # bad
    begin
      # an exception occurs here
    rescue SomeError
      # the rescue clause does absolutely nothing
    end
    
    # bad
    do_something rescue nil
  • Don't use exceptions for flow of control.

    # bad
    begin
      n / d
    rescue ZeroDivisionError
      puts 'Cannot divide by 0!'
    end
    
    # good
    if d.zero?
      puts 'Cannot divide by 0!'
    else
      n / d
    end
  • Avoid rescuing the Exception class. This will trap signals and calls to exit, requiring you to kill -9 the process.

    # bad
    begin
      # calls to exit and kill signals will be caught (except kill -9)
      exit
    rescue Exception
      puts "you didn't really want to exit, right?"
      # exception handling
    end
    
    # good
    begin
      # a blind rescue rescues from StandardError, not Exception as many
      # programmers assume.
    rescue => e
      # exception handling
    end
    
    # also good
    begin
      # an exception occurs here
    
    rescue StandardError => e
      # exception handling
    end
  • Put more specific exceptions higher up the rescue chain, otherwise they'll never be rescued from.

    # bad
    begin
      # some code
    rescue Exception => e
      # some handling
    rescue StandardError => e
      # some handling
    end
    
    # good
    begin
      # some code
    rescue StandardError => e
      # some handling
    rescue Exception => e
      # some handling
    end
  • Release external resources obtained by your program in an ensure block.

    f = File.open('testfile')
    begin
      # .. process
    rescue
      # .. handle error
    ensure
      f.close unless f.nil?
    end
  • Favor the use of exceptions for the standard library over introducing new exception classes.

Collections

  • Prefer literal array and hash creation notation (unless you need to pass parameters to their constructors, that is).

    # bad
    arr = Array.new
    hash = Hash.new
    
    # good
    arr = []
    hash = {}
  • Prefer %w to the literal array syntax when you need an array of strings.

    # bad
    STATES = ['draft', 'open', 'closed']
    
    # good
    STATES = %w(draft open closed)
  • Avoid the creation of huge gaps in arrays.

    arr = []
    arr[100] = 1 # now you have an array with lots of nils
  • Use Set instead of Array when dealing with unique elements. Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup.

  • Use symbols instead of strings as hash keys.

    # bad
    hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • Avoid the use of mutable object as hash keys.

  • Use the new 1.9 literal hash syntax in preference to the hashrocket syntax.

    # bad
    hash = { :one => 1, :two => 2, :three => 3 }
    
    # good
    hash = { one: 1, two: 2, three: 3 }
  • Rely on the fact that hashes in 1.9 are ordered.

  • Never modify a collection while traversing it.

Strings

  • Prefer string interpolation instead of string concatenation:

    # bad
    email_with_name = user.name + ' <' + user.email + '>'
    
    # good
    email_with_name = "#{user.name} <#{user.email}>"
  • Consider padding string interpolation code with space. It more clearly sets the code apart from the string.

    "#{ user.last_name }, #{ user.first_name }"
  • Prefer single-quoted strings when you don't need string interpolation or special symbols such as \t, \n, ', etc.

    # bad
    name = "Bozhidar"
    
    # good
    name = 'Bozhidar'
  • Don't use {} around instance variables being interpolated into a string.

    class Person
      attr_reader :first_name, :last_name
    
      def initialize( first_name, last_name )
        @first_name = first_name
        @last_name = last_name
      end
    
      # bad
      def to_s
        "#{@first_name} #{@last_name}"
      end
    
      # good
      def to_s
        "#@first_name #@last_name"
      end
    end
  • Avoid using String#+ when you need to construct large data chunks. Instead, use String#<<. Concatenation 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

Regular Expressions

  • Don't use regular expressions if you just need plain text search in string: string['text']

  • For simple constructions you can use regexp directly through string index.

    match = string[/regexp/]             # get content of matched regexp
    first_group = string[/text(grp)/, 1] # get content of captured group
    string[/text (grp)/, 1] = 'replace'  # string => 'text replace'
  • Use non capturing groups when you don't use captured result of parenthesis.

    /(first|second)/   # bad
    /(?:first|second)/ # good
  • Avoid using $1-9 as it can be hard to track what they contain. Named groups can be used instead.

    # bad
    /(regexp)/ =~ string
    ...
    process $1
    
    # good
    /(?<meaningful_var>regexp)/ =~ string
    ...
    process meaningful_var
  • Character classes have only few special characters you should care about: ^, -, \, ], so don't escape . or brackets in [].

  • 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 (not to be confused with \Z which is the equivalent of /\n?\z/).

    string = "some injection\nusername"
    string[/^username$/]   # matches
    string[/\Ausername\z/] # don't match
  • Use x modifier for complex regexps. 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
  • For complex replacements sub/gsub can be used with block or hash.

Percent Literals

  • Use %w freely.

    STATES = %w(draft open closed)
  • Use %() for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs.

    # bad (no interpolation needed)
    %(<div class="text">Some text</div>)
    # should be '<div class="text">Some text</div>'
    
    # bad (no double-quotes)
    %(This is #{quality} style)
    # should be "This is #{quality} style"
    
    # bad (multiple lines)
    %(<div>\n<span class="big">#{exclamation}</span>\n</div>)
    # should be a heredoc.
    
    # good (requires interpolation, has quotes, single line)
    %(<tr><td class="name">#{name}</td>)
  • Use %r only for regular expressions matching more than one '/' character.

    # bad
    %r(\s+)
    
    # still bad
    %r(^/(.*)$)
    # should be /^\/(.*)$/
    
    # good
    %r(^/blog/2011/(.*)$)
  • Avoid %q, %Q, %x, %s, and %W.

  • Prefer () as delimiters for all % literals.

Metaprogramming

  • Do not mess around in core classes when writing libraries. (Do not monkey patch them.)

  • The block form of class_eval is preferable to the string-interpolated form.

    • when you use the string-interpolated form, always supply __FILE__ and __LINE__, so that your backtraces make sense:

      class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
    • define_method is preferable to class_eval{ def ... }

  • When using class_eval (or other eval) with string interpolation, add a comment block showing its appearance if interpolated (a practice I learned from the rails code):

    # from activesupport/lib/active_support/core_ext/string/output_safety.rb
    UNSAFE_STRING_METHODS.each do |unsafe_method|
      if 'String'.respond_to?(unsafe_method)
        class_eval <<-EOT, __FILE__, __LINE__ + 1
          def #{unsafe_method}(*args, &block)       # def capitalize(*args, &block)
            to_str.#{unsafe_method}(*args, &block)  #   to_str.capitalize(*args, &block)
          end                                       # end
    
          def #{unsafe_method}!(*args)              # def capitalize!(*args)
            @dirty = true                           #   @dirty = true
            super                                   #   super
          end                                       # end
        EOT
      end
    end
  • avoid using method_missing for metaprogramming. Backtraces become messy; the behavior is not listed in #methods; misspelled method calls might silently work (nukes.launch_state = false). Consider using delegation, proxy, or define_method instead. If you must, use method_missing,

    • be sure to also define respond_to_missing?

    • only catch methods with a well-defined prefix, such as find_by_* -- make your code as assertive as possible.

    • call super at the end of your statement

    • delegate to assertive, non-magical methods:

      # bad
      def method_missing?(meth, *args, &block)
        if /^find_by_(?<prop>.*)/ =~ meth
          # ... lots of code to do a find_by
        else
          super
        end
      end
      
      # good
      def method_missing?(meth, *args, &block)
        if /^find_by_(?<prop>.*)/ =~ meth
          find_by(prop, *args, &block)
        else
          super
        end
      end
      
      # best of all, though, would to define_method as each findable attribute is declared

Misc

  • Write ruby -w safe code.

  • Avoid hashes as optional parameters. Does the method do too much?

  • Avoid methods longer than 10 LOC (lines of code). Ideally, most methods will be shorter than 5 LOC. Empty lines do not contribute to the relevant LOC.

  • Avoid parameter lists longer than three or four parameters.

  • If you really have to, add "global" methods to Kernel and make them private.

  • Use class instance variables instead of global variables.

    #bad
    $foo_bar = 1
    
    #good
    class Foo
      class << self
        attr_accessor :bar
      end
    end
    
    Foo.bar = 1
  • Avoid alias when alias_method will do.

  • Use OptionParser for parsing complex command line options and ruby -s for trivial command line options.

  • Code in a functional way, avoiding mutation when that makes sense.

  • Avoid needless metaprogramming.

  • Do not mutate arguments unless that is the purpose of the method.

  • Avoid more than three levels of block nesting.

  • Be consistent. In an ideal world, be consistent with these guidelines.

  • Use common sense.

Rails

Developing Rails applications

Configuration

  • Put custom initialization code in config/initializers. The code in initializers executes on application startup.

  • The initialization code for each gem should be in a separate file with the same name as the gem, for example carrierwave.rb, active_admin.rb, etc.

  • Adjust accordingly the settings for development, test and production environment (in the corresponding files under config/environments/)

    • Mark additional assets for precompilation (if any):

      ```Ruby
      # config/environments/production.rb
      # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
      config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )
      ```
      
  • Create an additional staging environment that closely resembles the production one.

Routing

  • When you need to add more actions to a RESTful resource (do you really need them at all?) use member and collection routes.

    # bad
    get 'subscriptions/:id/unsubscribe'
    resources :subscriptions
    
    # good
    resources :subscriptions, :member => { :unsubscribe => :get }
    
    # bad
    get 'photos/search'
    resources :photos
    
    # good
    resources :photos,:collection => { :search => :get }
  • Use nested routes to express better the relationship between ActiveRecord models.

    class Post < ActiveRecord::Base
      has_many :comments
    end
    
    class Comments < ActiveRecord::Base
      belongs_to :post
    end
    
    # routes.rb
    resources :posts do
      resources :comments
    end
  • Use namespaced routes to group related actions.

    namespace :admin do
      # Directs /admin/products/* to Admin::ProductsController
      # (app/controllers/admin/products_controller.rb)
      resources :products
    end
  • Never use the legacy wild controller route. This route will make all actions in every controller accessible via GET requests.

    # very bad
    match ':controller(/:action(/:id(.:format)))'

Controllers

  • Keep the controllers skinny - they should only retrieve data for the view layer and shouldn't contain any business logic (all the business logic should naturally reside in the model).
  • Each controller action should (ideally) invoke only one method other than an initial find or new.
  • Share no more than two instance variables between a controller and a view.

Models

  • Introduce non-ActiveRecord model classes freely.

  • Name the models with meaningful (but short) names without abbreviations.

  • If you need model objects that support ActiveRecord behavior like validation use the ActiveAttr gem.

    class Message
      include ActiveAttr::Model
    
      attribute :name
      attribute :email
      attribute :content
      attribute :priority
    
      attr_accessible :name, :email, :content
    
      validates_presence_of :name
      validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
      validates_length_of :content, :maximum => 500
    end

    For a more complete example refer to the RailsCast on the subject.

ActiveRecord

  • Avoid altering ActiveRecord defaults (table names, primary key, etc) unless you have a very good reason (like a database that's not under your control).

  • Group macro-style methods (has_many, validates, etc) in the beginning of the class definition.

  • Prefer has_many :through to has_and_belongs_to_many. Using has_many :through allows additional attributes and validations on the join model.

    # using has_and_belongs_to_many
    class User < ActiveRecord::Base
      has_and_belongs_to_many :groups
    end
    
    class Group < ActiveRecord::Base
      has_and_belongs_to_many :users
    end
    
    # prefered way - using has_many :through
    class User < ActiveRecord::Base
      has_many :memberships
      has_many :groups, through: :memberships
    end
    
    class Membership < ActiveRecord::Base
      belongs_to :user
      belongs_to :group
    end
    
    class Group < ActiveRecord::Base
      has_many :memberships
      has_many :users, through: :memberships
    end
  • When a custom validation is used more than once or the validation is some regular expression mapping, create a custom validator file.

    # bad
    class Person
      validates :email, format: { with: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i }
    end
    
    # good
    class EmailValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
      end
    end
    
    class Person
      validates :email, email: true
    end
  • All custom validators should be moved to a shared gem.

  • Use named scopes freely.

  • When a named scope, defined with a lambda and parameters, becomes too complicated it is preferable to make a class method instead which serves the same purpose of the named scope and returns and ActiveRecord::Relation object.

  • Beware of the behavior of the update_attribute method. It doesn't run the model validations (unlike update_attributes) and could easily corrupt the model state.

  • Use user-friendly URLs. Show some descriptive attribute of the model in the URL rather than its id. There is more than one way to achieve this:

    • Override the to_param method of the model. This method is used by Rails for constructing an URL to the object. The default implementation returns the id of the record as a String. It could be overridden to include another human-readable attribute.

      ```Ruby
      class Person
        def to_param
          "#{id} #{name}".parameterize
        end
      end
      ```
      In order to convert this to a URL-friendly value, `parameterize` should be called on the string. The `id` of the
      object needs to be at the beginning so that it could be found by the `find` method of ActiveRecord.
      
    • Use the friendly_id gem. It allows creation of human-readable URLs by using some descriptive attribute of the model instead of its id.

      ```Ruby
      class Person
        extend FriendlyId
        friendly_id :name, use: :slugged
      end
      ```
      
      Check the [gem documentation](https://github.com/norman/friendly_id) for more information about its usage.
      

Migrations

  • Keep the schema.rb under version control.

  • Use rake db:schema:load instead of rake db:migrate to initialize an empty database.

  • Use rake db:test:prepare to update the schema of the test database.

  • Avoid setting defaults in the tables themselves. Use the model layer instead.

    def amount
      self[:amount] || 0
    end

    While the use of self[:attr_name] is considered fairly idiomatic, you might also consider using the slightly more verbose (and arguably more readable) read_attribute instead:

    def amount
      read_attribute( :amount ) || 0
    end
  • When writing constructive migrations (adding tables or columns), ensure that the down mehtod is written

  • When necessary, use execute("RAW SQL") to achieve missing functionality

Views

  • Never call the model layer directly from a view.
  • Never make complex formatting in the views, export the formatting to a method in the view helper or the model.
  • Mitigate code duplication by using partial templates and layouts.
  • Add client side validation for the custom validators. The steps to do this are:
    • Declare a custom validator which extends ClientSideValidations::Middleware::Base

      ```Ruby
      module ClientSideValidations::Middleware
        class Email < Base
          def response
            if request.params[:email] =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
              self.status = 200
            else
              self.status = 404
            end
            super
          end
        end
      end
      ```
      
    • Create a new file public/javascripts/rails.validations.custom.js.coffee and add a reference to it in your application.js.coffee file:

      ```Ruby
      # app/assets/javascripts/application.js.coffee
      #= require rails.validations.custom
      ```
      
    • Add your client-side validator:

      ```Ruby
      #public/javascripts/rails.validations.custom.js.coffee
      clientSideValidations.validators.remote['email'] = (element, options) ->
        if $.ajax({
          url: '/validators/email.json',
          data: { email: element.val() },
          async: false
        }).status == 404
          return options.message || 'invalid e-mail format'
      ```
      

Internationalization

  • No strings or other locale specific settings should be used in the views, models and controllers. These texts should be moved to the locale files in the config/locales directory.

  • When the labels of an ActiveRecord model need to be translated, use the activerecord scope:

    en:
      activerecord:
        models:
          user: Member
        attributes:
          user:
            name: "Full name"
    

    Then User.model_name.human will return "Member" and User.human_attribute_name("name") will return "Full name". These translations of the attributes will be used as labels in the views.

  • Separate the texts used in the views from translations of ActiveRecord attributes. Place the locale files for the models in a folder models and the texts used in the views in folder views.

    • When organization of the locale files is done with additional directories, these directories must be described in the application.rb file in order to be loaded.

      ```Ruby
      # config/application.rb
      config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
      ```
      
  • Place the shared localization options, such as date or currency formats, in files under the root of the locales directory.

  • Use the short form of the I18n methods: I18n.t instead of I18n.translate and I18n.l instead of I18n.localize.

  • Use "lazy" lookup for the texts used in views. Let's say we have the following structure:

    en:
      users:
        show:
          title: "User details page"
    

    The value for users.show.title can be looked up in the template app/views/users/show.html.haml like this:

    = t '.title'
  • Use the dot-separated keys in the controllers and models instead of specifying the :scope option. The dot-separated call is easier to read and trace the hierarchy.

    # use this call
    I18n.t 'activerecord.errors.messages.record_invalid'
    
    # instead of this
    I18n.t :record_invalid, :scope => [:activerecord, :errors, :messages]
  • More detailed information about the Rails i18n can be found in the [Rails Guides] (http://guides.rubyonrails.org/i18n.html)

Mailers

  • Name the mailers SomethingMailer. Without the Mailer suffix it isn't immediately apparent what's a mailer and which views are related to the mailer.

  • Provide both HTML and plain-text view templates.

  • Enable errors raised on failed mail delivery in your development environment. The errors are disabled by default.

    # config/environments/development.rb
    
    config.action_mailer.raise_delivery_errors = true
  • Use smtp.gmail.com for SMTP server in the development environment (unless you have local SMTP server, of course).

    # config/environments/development.rb
    
    config.action_mailer.smtp_settings = {
      address: 'smtp.gmail.com',
      # more settings
    }
  • Provide default settings for the host name.

    # config/environments/development.rb
    config.action_mailer.default_url_options = {host: "#{local_ip}:3000"}
    
    
    # config/environments/production.rb
    config.action_mailer.default_url_options = {host: 'your_site.com'}
    
    # in your mailer class
    default_url_options[:host] = 'your_site.com'
  • If you need to use a link to your site in an email, always use the _url, not _path methods. The _url methods include the host name and the _path methods don't.

    # wrong
    You can always find more info about this course
    = link_to 'here', url_for(course_path(@course))
    
    # right
    You can always find more info about this course
    = link_to 'here', url_for(course_url(@course))
  • Format the from and to addresses properly. Use the following format:

    # in your mailer class
    default from: 'Your Name <info@your_site.com>'
  • Make sure that the e-mail delivery method for your test environment is set to test:

    # config/environments/test.rb
    
    config.action_mailer.delivery_method = :test
  • The delivery method for development and production should be smtp:

    # config/environments/development.rb, config/environments/production.rb
    
    config.action_mailer.delivery_method = :smtp
  • When sending html emails all styles should be inline, as some mail clients have problems with external styles. This however makes them harder to maintain and leads to code duplication. There are two similar gems that transform the styles and put them in the corresponding html tags: premailer-rails3 and roadie.

  • Sending emails while generating page response should be avoided. It causes delays in loading of the page and request can timeout if multiple email are send. To overcome this emails can be send in background process with the help of delayed_job gem.

Bundler

  • Put gems used only for development or testing in the appropriate group in the Gemfile.

  • Use only established gems in your projects. If you're contemplating on including some little-known gem you should do a careful review of its source code first.

  • OS-specific gems will by default result in a constantly changing Gemfile.lock for projects with multiple developers using different operating systems. Add all OS X specific gems to a darwin group in the Gemfile, and all Linux specific gems to a linux group:

    # Gemfile
    group :darwin do
      gem 'rb-fsevent'
      gem 'growl'
    end
    
    group :linux do
      gem 'rb-inotify'
    end

    To require the appropriate gems in the right environment, add the following to config/application.rb:

    platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym
    Bundler.require(platform)
  • Do not remove the Gemfile.lock from version control. This is not some randomly generated file - it makes sure that all of your team members get the same gem versions when they do a bundle install.

Priceless Gems

One of the most important programming principles is "Don't reinvent the wheel!". If you're faced with a certain task you should always look around a bit for existing solutions, before unrolling your own. Here's a list of some "priceless" gems :

  • active_admin - With ActiveAdmin the creation of admin interface for your Rails app is child's play. You get a nice dashboard, CRUD UI and lots more. Very flexible and customizable.
  • capybara - Capybara aims to simplify the process of integration testing Rack applications, such as Rails, Sinatra or Merb. Capybara simulates how a real user would interact with a web application. It is agnostic about the driver running your tests and currently comes with Rack::Test and Selenium support built in. HtmlUnit, WebKit and env.js are supported through external gems. Works great in combination with RSpec & Cucumber.
  • carrierwave - the ultimate file upload solution for Rails. Support both local and cloud storage for the uploaded files (and many other cool things). Integrates great with ImageMagick for image post-processing.
  • client_side_validations - Fantastic gem that automatically creates JavaScript client-side validations from your existing server-side model validations. Highly recommended!
  • compass-rails - Great gem that adds support for some css frameworks. Includes collection of sass mixins that reduces code of css files and help fight with browser incompatibilities.
  • cucumber-rails - Cucumber is the premium tool to develop feature tests in Ruby. cucumber-rails provides Rails integration for Cucumber.
  • devise - Devise is full-featured authentication solution for Rails applications. In most cases it's preferable to use devise to unrolling your custom authentication solution.
  • fabrication - a great fixture replacement (editor's choice).
  • factory_girl - an alternative to fabrication. Nice and mature fixture replacement. Spiritual ancestor of fabrication.
  • faker - handy gem to generate dummy data (names, addresses, etc).
  • feedzirra - Very fast and flexible RSS/Atom feed parser.
  • friendly_id - Allows creation of human-readable URLs by using some descriptive attribute of the model instead of its id.
  • guard - fantastic gem that monitors file changes and invokes tasks based on them. Loaded with lots of useful extension. Far superior to autotest and watchr.
  • haml-rails - haml-rails provides Rails integration for Haml.
  • haml - HAML is a concise templating language, considered by many (including yours truly) to be far superior to Erb.
  • kaminari - Great paginating solution.
  • machinist - Fixtures aren't fun. Machinist is.
  • rspec-rails - RSpec is a replacement for Test::MiniTest. I cannot recommend highly enough RSpec. rspec-rails provides Rails integration for RSpec.
  • simple_form - once you've used simple_form (or formtastic) you'll never want to hear about Rails's default forms. It has a great DSL for building forms and no opinion on markup.
  • simplecov-rcov - RCov formatter for SimpleCov. Useful if you're trying to use SimpleCov with the Hudson contininous integration server.
  • simplecov - code coverage tool. Unlike RCov it's fully compatible with Ruby 1.9. Generates great reports. Must have!
  • slim - Slim is a concise templating language, considered by many far superior to HAML (not to mention Erb). The only thing stopping me from using Slim massively is the lack of good support in major editors/IDEs. Its performance is phenomenal.
  • spork - A DRb server for testing frameworks (RSpec / Cucumber currently) that forks before each run to ensure a clean testing state. Simply put it preloads a lot of test environment and as consequence the startup time of your tests in greatly decreased. Absolute must have!
  • sunspot - SOLR powered full-text search engine.

This list is not exhaustive and other gems might be added to it along the road. All of the gems on the list are field tested, have active development and community and are known to be of good code quality.

Flawed Gems

This is a list of gems that are either problematic or superseded by other gems. You should avoid using them in your projects.

  • rmagick - this gem is notorious for its memory consumption. Use minimagick instead.
  • autotest - old solution for running tests automatically. Far inferior to guard and watchr.
  • rcov - code coverage tool, not compatible with Ruby 1.9. Use SimpleCov instead.
  • therubyracer - the use of this gem in production is strongly discouraged as it uses a very large amount of memory. I'd suggest using Mustang instead.

This list is also a work in progress. Please, let me know if you know other popular, but flawed gems.

Managing processes

  • If your projects depends on various external processes use foreman to manage them.

Testing Rails applications

The best approach to implementing new features is probably the BDD approach. You start out by writing some high level feature tests (generally written using Cucumber), then you use these tests to drive out the implementation of the feature. First you write view specs for the feature and use those specs to create the relevant views. Afterwards you create the specs for the controller(s) that will be feeding data to the views and use those specs to implement the controller. Finally you implement the models specs and the models themselves.

Cucumber

  • Tag your pending scenarios with @wip (work in progress). These scenarios will not be taken into account and will not be marked as failing. When finishing the work on a pending scenario and implementing the functionality it tests, the tag @wip should be removed in order to include this scenario in the test suite.

  • Setup your default profile to exclude the scenarios tagged with @javascript. They are testing using the browser and disabling them is recommended to increase the regular scenarios execution speed.

  • Setup a separate profile for the scenarios marked with @javascript tag.

    • The profiles can be configured in the cucumber.yml file.

      ```Ruby
      # definition of a profile:
      profile_name: --tags @tag_name
      ```
      
    • A profile is run with the command:

      ```
      cucumber -p profile_name
      ```
      
  • If using fabrication for fixtures replacement, use the predefined fabrication steps

  • Do not use the old web_steps.rb step definitions! The web steps were removed from the latest version of Cucumber. Their usage leads to the creation of verbose scenarios that do not properly reflect the application domain.

  • When checking for the presence of an element with visible text (link, button, etc.) check for the text, not the element id. This can detect problems with the i18n.

  • Create separate features for different functionality regarding the same kind of objects:

    # bad
    Feature: Articles
    # ... feature  implementation ...
    
    # good
    Feature: Article Editing
    # ... feature  implementation ...
    
    Feature: Article Publishing
    # ... feature  implementation ...
    
    Feature: Article Search
    # ... feature  implementation ...
  • Each feature has three main components

    • Title
    • Narrative - a short explanation what the feature is about.
    • Acceptance criteria - the set of scenarios each made up of individual steps.
  • The most common format is known as the Connextra format.

    In order to [benefit] ...
    A [stakeholder]...
    Wants to [feature] ...

This format is the most common but is not required, the narrative can be free text depending on the complexity of the feature.

  • Use Scenario Outlines freely to keep the scenarios DRY.

    Scenario Outline: User cannot register with invalid e-mail
      When I try to register with an email "<email>"
      Then I should see the error message "<error>"
    
    Examples:
      |email         |error                 |
      |              |The e-mail is required|
      |invalid email |is not a valid e-mail |
  • The steps for the scenarios are in .rb files under the step_definitions directory. The naming convention for the steps file is [description]_steps.rb. The steps can be separated into different files based on different criterias. It is possible to have one steps file for each feature (home_page_steps.rb). There also can be one steps file for all features for a particular object (articles_steps.rb).

  • Use multiline step arguments to avoid repetition

    Scenario: User profile
      Given I am logged in as a user "John Doe" with an e-mail "[email protected]"
      When I go to my profile
      Then I should see the following information:
        |First name|John         |
        |Last name |Doe          |
        |E-mail    |user@test.com|
    
    # the step:
    Then /^I should see the following information:$/ do |table|
      table.raw.each do |field, value|
        find_field(field).value.should =~ /#{value}/
      end
    end
  • Use compound steps to keep the scenario DRY

    # ...
    When I subscribe for news from the category "Technical News"
    # ...
    
    # the step:
    When /^I subscribe for news from the category "([^"]*)"$/ do |category|
      steps %Q{
        When I go to the news categories page
        And I select the category #{category}
        And I click the button "Subscribe for this category"
        And I confirm the subscription
      }
    end
  • Always use the Capybara negative matchers instead of should_not with positive, they will retry the match for given timeout allowing you to test ajax actions. See Capybara's README for more explanation

RSpec

  • Use just one expectation per example.

    # bad
    describe ArticlesController do
      #...
    
      describe 'GET new' do
        it 'assigns new article and renders the new article template' do
          get :new
          assigns[:article].should be_a_new Article
          response.should render_template :new
        end
      end
    
      # ...
    end
    
    # good
    describe ArticlesController do
      #...
    
      describe 'GET new' do
        it 'assigns a new article' do
          get :new
          assigns[:article].should be_a_new Article
        end
    
        it 'renders the new article template' do
          get :new
          response.should render_template :new
        end
      end
    
    end
  • Make heavy use of describe and context

  • Name the describe blocks as follows:

    • use "description" for non-methods

    • use pound "#method" for instance methods

    • use dot ".method" for class methods

      class Article
        def summary
          #...
        end
      
        def self.latest
          #...
        end
      end
      
      # the spec...
      describe Article
        describe '#summary'
          #...
        end
      
        describe '.latest'
          #...
        end
      end
  • Use fabricators to create test objects.

  • Make heavy use of mocks and stubs

    # mocking a model
    article = mock_model(Article)
    
    # stubbing a method
    Article.stub(:find).with(article.id).and_return(article)
  • When mocking a model, use the as_null_object method. It tells the output to listen only for messages we expect and ignore any other messages.

    article = mock_model(Article).as_null_object
  • Use let blocks instead of before(:all) blocks to create data for the spec examples. let blocks get lazily evaluated.

    # use this:
    let(:article) { Fabricate(:article) }
    
    # ... instead of this:
    before(:each) { @article = Fabricate(:article) }
  • Use subject when possible

    describe Article do
      subject { Fabricate(:article) }
    
      it 'is not published on creation' do
        subject.should_not be_published
      end
    end
  • Use specify if possible. It is a synonym of it but is more readable when there is no docstring.

    # bad
    describe Article do
      before { @article = Fabricate(:article) }
    
      it 'is not published on creation' do
        @article.should_not be_published
      end
    end
    
    # good
    describe Article do
      let(:article) { Fabricate(:article) }
      specify { article.should_not be_published }
    end
  • Use its when possible

    # bad
    describe Article do
      subject { Fabricate(:article) }
    
      it 'has the current date as creation date' do
        subject.creation_date.should == Date.today
      end
    end
    
    # good
    describe Article do
      subject { Fabricate(:article) }
      its(:creation_date) { should == Date.today }
    end

Views

  • The directory structure of the view specs spec/views matches the one in app/views. For example the specs for the views in app/views/users are placed in spec/views/users.

  • The naming convention for the view specs is adding _spec.rb to the view name, for example the view _form.html.haml has a corresponding spec _form.html.haml_spec.rb.

  • spec_helper.rb need to be required in each view spec file.

  • The outer describe block uses the path to the view without the app/views part. This is used by the render method when it is called without arguments.

    # spec/views/articles/new.html.haml_spec.rb
    require 'spec_helper'
    
    describe 'articles/new.html.haml' do
      # ...
    end
  • Always mock the models in the view specs. The purpose of the view is only to display information.

  • The method assign supplies the instance variables which the view uses and are supplied by the controller.

    # spec/views/articles/edit.html.haml_spec.rb
    describe 'articles/edit.html.haml' do
    it 'renders the form for a new article creation' do
      assign(
        :article,
        mock_model(Article).as_new_record.as_null_object
      )
      render
      rendered.should have_selector('form',
        method: 'post',
        action: articles_path
      ) do |form|
        form.should have_selector('input', type: 'submit')
      end
    end
  • Prefer the capybara negative selectors over should_not with the positive.

    # bad
    page.should_not have_selector('input', type: 'submit')
    page.should_not have_xpath('tr')
    
    # good
    page.should have_no_selector('input', type: 'submit')
    page.should have_no_xpath('tr')
  • When a view uses helper methods, these methods need to be stubbed. Stubbing the helper methods is done on the template object:

    # app/helpers/articles_helper.rb
    class ArticlesHelper
      def formatted_date(date)
        # ...
      end
    end
    
    # app/views/articles/show.html.haml
    = "Published at: #{formatted_date(@article.published_at)}"
    
    # spec/views/articles/show.html.haml_spec.rb
    describe 'articles/show.html.html' do
      it 'displays the formatted date of article publishing'
        article = mock_model(Article, published_at: Date.new(2012, 01, 01))
        assign(:article, article)
    
        template.stub(:formatted_date).with(article.published_at).and_return '01.01.2012'
    
        render
        rendered.should have_content('Published at: 01.01.2012')
      end
    end
  • The helpers specs are separated from the view specs in the spec/helpers directory.

Controllers

  • Mock the models and stub their methods. Testing the controller should not depend on the model creation.

  • Test only the behaviour the controller should be responsible about:

    • Execution of particular methods

    • Data returned from the action - assigns, etc.

    • Result from the action - template render, redirect, etc.

      ```Ruby
      # Example of a commonly used controller spec
      # spec/controllers/articles_controller_spec.rb
      # We are interested only in the actions the controller should perform
      # So we are mocking the model creation and stubbing its methods
      # And we concentrate only on the things the controller should do
      
      describe ArticlesController do
        # The model will be used in the specs for all methods of the controller
        let(:article) { mock_model(Article) }
      
        describe 'POST create' do
          before { Article.stub(:new).and_return(article) }
      
          it 'creates a new article with the given attributes' do
            Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
            post :create, message: { title: 'The New Article Title' }
          end
      
          it 'saves the article' do
            article.should_receive(:save)
            post :create
          end
      
          it 'redirects to the Articles index' do
            article.stub(:save)
            post :create
            response.should redirect_to(action: 'index')
          end
        end
      end
      ```
      
  • Use context when the controller action has different behaviour depending on the received params.

    # A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.
    
    describe ArticlesController do
      let(:article) { mock_model(Article) }
    
      describe 'POST create' do
        before { Article.stub(:new).and_return(article) }
    
        it 'creates a new article with the given attributes' do
          Article.should_receive(:new).with(title: 'The New Article Title').and_return(article)
          post :create, article: { title: 'The New Article Title' }
        end
    
        it 'saves the article' do
          article.should_receive(:save)
          post :create
        end
    
        context 'when the article saves successfully' do
          before { article.stub(:save).and_return(true) }
    
          it 'sets a flash[:notice] message' do
            post :create
            flash[:notice].should eq('The article was saved successfully.')
          end
    
          it 'redirects to the Articles index' do
            post :create
            response.should redirect_to(action: 'index')
          end
        end
    
        context 'when the article fails to save' do
          before { article.stub(:save).and_return(false) }
    
          it 'assigns @article' do
            post :create
            assigns[:article].should be_eql(article)
          end
    
          it 're-renders the "new" template' do
            post :create
            response.should render_template('new')
          end
        end
      end
    end

Models

  • Do not mock the models in their own specs.

  • Use fabrication to make real objects.

  • It is acceptable to mock other models or child objects.

  • Create the model for all examples in the spec to avoid duplication.

    describe Article
      let(:article) { Fabricate(:article) }
    end
  • Add an example ensuring that the fabricated model is valid.

    describe Article
      it 'is valid with valid attributes' do
        article.should be_valid
      end
    end
  • When testing validations, use have(x).errors_on to specify the attibute which should be validated. Using be_valid does not guarantee that the problem is in the intended attribute.

    # bad
    describe '#title'
      it 'is required' do
        article.title = nil
        article.should_not be_valid
      end
    end
    
    # prefered
    describe '#title'
      it 'is required' do
        article.title = nil
        article.should have(1).error_on(:title)
      end
    end
  • Add a separate describe for each attribute which has validations.

    describe Article
      describe '#title'
        it 'is required' do
          article.title = nil
          article.should have(1).error_on(:title)
        end
      end
    end
  • When testing uniqueness of a model attribute, name the other object another_object.

    describe Article
      describe '#title'
        it 'is unique' do
          another_article = Fabricate.build(:article, title: article.title)
          article.should have(1).error_on(:title)
        end
      end
    end

Mailers

  • The model in the mailer spec should be mocked. The mailer should not depend on the model creation.
  • The mailer spec should verify that:
    • the subject is correct

    • the receiver e-mail is correct

    • the e-mail is sent to the right e-mail address

    • the e-mail contains the required information

      describe SubscriberMailer
        let(:subscriber) { mock_model(Subscription, email: '[email protected]', name: 'John Doe') }
      
        describe 'successful registration email'
          subject { SubscriptionMailer.successful_registration_email(subscriber) }
      
          its(:subject) { should == 'Successful Registration!' }
          its(:from) { should == ['info@your_site.com'] }
          its(:to) { should == [subscriber.email] }
      
          it 'contains the subscriber name' do
            subject.body.encoded.should match(subscriber.name)
          end
        end
      end

Uploaders

  • What we can test about an uploader is whether the images are resized correctly. Here is a sample spec of a carrierwave image uploader:

    # rspec/uploaders/person_avatar_uploader_spec.rb
    require 'spec_helper'
    require 'carrierwave/test/matchers'
    
    describe PersonAvatarUploader do
      include CarrierWave::Test::Matchers
    
      # Enable images processing before executing the examples
      before(:all) do
        UserAvatarUploader.enable_processing = true
      end
    
      # Create a new uploader. The model is mocked as the uploading and resizing images does not depend on the model creation.
      before(:each) do
        @uploader = PersonAvatarUploader.new(mock_model(Person).as_null_object)
        @uploader.store!(File.open(path_to_file))
      end
    
      # Disable images processing after executing the examples
      after(:all) do
        UserAvatarUploader.enable_processing = false
      end
    
      # Testing whether image is no larger than given dimensions
      context 'the default version' do
        it 'scales down an image to be no larger than 256 by 256 pixels' do
          @uploader.should be_no_larger_than(256, 256)
        end
      end
    
      # Testing whether image has the exact dimensions
      context 'the thumb version' do
        it 'scales down an image to be exactly 64 by 64 pixels' do
          @uploader.thumb.should have_dimensions(64, 64)
        end
      end
    end

ruby-style-guide's People

Contributors

bbatsov avatar kalbasit avatar mitio avatar marcandre avatar ezii-jester-data avatar dreoliv avatar gazay avatar asifkalam avatar eregon avatar dquimper avatar mrtazz avatar heliosmaster avatar geoffyoungs avatar j16r avatar jmettraux avatar wersimmon avatar julio avatar originell avatar gonzih avatar stevenharman avatar tmaier avatar wteuber avatar meh avatar

Stargazers

Angus H. avatar Adam Palmblad avatar

Watchers

Adam Palmblad avatar James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.