Rails

When seeking guidance on how to design a feature, consult this guide, follow existing conventions in the codebase, or follow Rails conventions. The API is your friend.

Gems

These are the gems we typically use.

  • clearance for authentication
  • puma as the application server
  • email_validator for validation email addresses (included with clearance)
  • acts_as_tree for modelling tree structures in ActiveRecord
  • acts_as_list for modelling list structures in ActiveRecord
  • kaminari for pagination
  • filterrific for providing filter options on views
  • exception_notification for notifying us of application errors
  • capistrano for applicaton deployment
  • state_machines-activerecord for modelling state machines in ActiveRecord
  • kramdown for rendering Markdown
  • sidekiq for background job processing
  • paperclip for file attachments
  • minitest for testing

Defaults

Use the default configuration files in /rails for new projects.

Models

Use the following format for models.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class User

  ##
  # Constants
  #

  ##
  # Attributes
  #

  ##
  # Extensions
  #

  ##
  # Associations
  #

  ##
  # Validations
  #

  ##
  # Callbacks
  #

  ##
  # Scopes
  #

  ##
  # Class methods
  #

  ##
  # Instance methods
  #

end

Validations

Use the new syntax for validations

1
2
3
4
5
# Bad
validates_presence_of :title

# Good
validates :title, presence: true

Permissions

Use this pattern for doing permissions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
module User::HasPermissions

  extend ActiveSupport::Concern

  class_methods do

    # Checks if `actor` can list these resources.
    #
    # @param actor [User]
    # @return [Boolean] true if `actor` can list these resources
    def listable_by?(actor)
      actor.is_a?(User)
    end

    # Checks if `actor` can create an instance of this resource.
    #
    # @param actor [User]
    # @return [Boolean] true if `actor` can create an instance of this resource
    def creatable_by?(actor)
      actor.is_a?(User)
    end

  end

  # Checks if `actor` can view an instance of this resource.
  #
  # @param actor [User]
  # @return [Boolean] true if `actor` can view an instance of this resource
  def viewable_by?(actor)
    actor.is_a?(User)
  end

  # Checks if `actor` can create this instance of this resource.
  #
  # @param actor [User]
  # @return [Boolean] true if `actor` can create this instance of this resource
  def creatable_by?(actor)
    actor.is_a?(User)
  end

  # Checks if `actor` can update this instance of this resource.
  #
  # @param actor [User]
  # @return [Boolean] true if `actor` can update this instance of this resource
  def updatable_by?(actor)
    actor.is_a?(User)
  end

  # Checks if `actor` can destroy this instance of this resource.
  #
  # @param actor [User]
  # @return [Boolean] true if `actor` can destroy this instance of this resource
  def destroyable_by?(actor)
    actor.is_a?(User)
  end

end

Controllers

  • Use symbols instead of numerical HTTP status codes.

Inheritance

Each controller namespace should have a BaseController which all controllers in that namespace inherit from. The BaseController is a place to setup any objects, do permission checks, etc.

1
2
3
4
admin/
  base_controller.rb
  users_controller.rb
  posts_controller.rb

Documentation

Often an application requires some additional documentation, such as how-to guides for performing a certain task, or development notes about a large change that occurred. Documentation like this should be stored in doc/how_to, and doc/dev_notes respectively. Prefix documents with ymd timestamps (i.e. 20160413_example_title.txt). Use whatever extension makes sense, such as .rb, .md, .sql, .txt, etc.

Here’s an example doc/ folder structure.

1
2
3
4
5
6
7
8
doc/
  how_to/
    20100101_generate_user_report.sql
    20110101_mark_user_as_denied.rb
  dev_notes/
    20120101_transition_to_postgres.md
    20130101_reset_postgres_sequences.sql
    20140101_pci_compliance.md

Over time, doc/dev_notes/ may become quite large, and contain documentation that isn’t relevant to current development. When this occurs, move older documentation to doc/dev_notes/archive to keep the working set of documentation small and manageable.

Mailers

  • Name mailers with the Mailer suffix (i.e. CommentMailer).

ActiveSupport

  • Use Ruby 2.3’s &. over try.
  • Prefer Ruby’s built-in methods over ActiveSupports.

Time

  • Set the timezone in config/application.rb.
  • Use Time.current over Time.now.
  • Prefer the use of localizations for custom time formats over an initializer.

    In config/locales/en.yml, add your time formats.

    1
    2
    3
    4
    
    en:
      time:
        formats:
          full: "%B %-d, %Y @ %-l:%M%P"
    

    Use the l or localize method to use the format.

    1
    2
    
    # In a template, use the time format like this
    <%= l datetime_object, format: :full %>
    

Testing

1
bundle exec rspec

Wherever possible use locals. This will make spec maintenance a minimum.

1
2
3
4
5
6
7
...

scenario "foo" do
  expect(page).to have_content I18n.t("page.controller.action.success")
end

...

using il8n-tasks https://github.com/glebm/i18n-tasks#usage

1
i18n-tasks add-missing

Include this in the gem file, review rails/rails_helper.example.rb and rails/spec_helper.example.rb for defaults or https://github.com/rspec/rspec-rails#installation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
group :test do

  gem 'database_cleaner'

end

group :development do

  # i18n-tasks helps you find and manage missing and unused translations. https://github.com/glebm/i18n-tasks#usage
  gem 'i18n-tasks', '~> 0.9.12'

end

group :development, :test do

  # test suit
  gem 'capybara'
  gem 'rspec-rails', '~> 3.5'
  gem "factory_girl_rails", "~> 4.0"
  gem 'shoulda-matchers', '~> 3.1'

end

If you are using clearance gem, you can generate specs to start off from https://github.com/thoughtbot/clearance#ready-made-feature-specs

1
rails generate clearance:specs