Ruby Code Style

Use rubocop, reek and flay tools to detect issues in code. If for some cases default configuration does not make sense and whole team agrees, then local exception can be made.

Rubocop

# Write longer lines because in many cases code looks better
Metrics/LineLength:
  Max: 120

# Default settings are too strict for complex rails applications
#
# Allow more complex methods
Metrics/AbcSize:
  Max: 20

# Allow longer methods
Metrics/MethodLength:
  CountComments: false
  Max: 15

# We prefer to write multi-line expect blocks with curly braces in specs
Style/BlockDelimiters:
  Exclude:
    - 'spec/**/*'

Rails:
  Enabled: true

AllCops:
  TargetRubyVersion: 2.3
  Include:
    - '**/Rakefile'
    - '**/config.ru'

  # Don't lint auto-generated files
  Exclude:
    - 'db/**/*'
    - 'script/**/*'
    - 'bin/**/*'
    - !ruby/regexp /client\/node_modules/

Style/CollectionMethods:
  PreferredMethods:
    # Don't confuse with ActiveRecord#find
    find: 'detect'

# Write full parameter name instead of few letters
Style/SingleLineBlockParams:
  Enabled: false

# Performance gain is not worth the hassle
Performance/Casecmp:
  Enabled: false

# Lots of gems and own code does not support this yet
Style/FrozenStringLiteralComment:
  Enabled: false

# Allow passing classes to ActiveRecord scopes
Rails/ScopeArgs:
  Enabled: false

Reek

# Nature of rails helpers and validators require feature envy exception
FeatureEnvy:
  exclude:
  - !ruby/regexp /Helper/
  - !ruby/regexp /Validator/

# Nature of rails helpers, validators, background jobs and concerns require utility function exception
UtilityFunction:
  exclude:
  - !ruby/regexp /Helper/
  - !ruby/regexp /Validator/
  - !ruby/regexp /Job/
  - !ruby/regexp /ClassMethods/

# Rails mailers tend to get quite big and have lots of instance variables
TooManyInstanceVariables:
  exclude:
    - !ruby/regexp /Mailer/

# Rails helpers tend to have lots of methods that might look similar
DataClump:
  exclude:
  - !ruby/regexp /Helper/

# Allowing one level of nesting is way too restrictive.
NestedIterators:
  max_allowed_nesting: 2

# No need for repetitive comments to namespace modules
IrresponsibleModule:
  enabled: false

"app/controllers":
  TooManyStatements: # Rails actions tend to have more statements, particularly within respond_to blocks
    max_statements: 7

Flay

  • Don’t check views with flay - most of the reported issues are false positives (input fields, forms, links, image tags, etc).
  • Don’t check controllers with flay - most of the reported issues are false positives (permitted params, respond_to blocks, etc).

Attribute accessors

  • Use attr_reader to define attributes and expose them publicly only if it is needed
  • Do not expose attr_writer publicly
  • Do not expose attr_accessor publicly (only exception is for form objects; otherwise, form objects won’t work correctly)
  • Use defined attr_reader, attr_writer and attr_accessor attributes inside the class instead of instance variables
  • Use instance variables only when defining custom setters/getters, caching and inside mailers

A simple example:

  • The class below uses attribute writers to save passed parameters lang_doc and learn_doc
  • It also exposes those attributes publicly (let’s say it is needed)
  • LanguageDocumentComposer also sets errors attribute through an attribute writer and counter attribute through an attribute accessor
  • errors attribute is exposed publicly whereas counter attribute is only used internally
  • Instance variables aren’t used at all
  class LanguageDocumentComposer
    attr_reader :lang_doc, :learn_doc, :errors

    def initialize(lang_doc, learn_doc)
      self.lang_doc = lang_doc
      self.learn_doc = learn_doc
      self.errors = []
      self.counter = 0
    end

    def valid?
      errors.blank?
    end

    def compose
      if lang_doc.valid? && learn_doc.valid?
        composed_doc
      else
        errors.concat(lang_doc.errors)
        errors.concat(learn_doc.errors)

        nil
      end
    end

    private

    def composed_doc

       ...

      counter += 1;
    end

    attr_writer :lang_doc, :learn_doc, :errors
    attr_accessor :counter
  end