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.
These are the gems we typically use.
clearance
for authenticationpuma
as the application serveremail_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 paginationfilterrific
for providing filter options on viewsexception_notification
for notifying us of application errorscapistrano
for applicaton deploymentstate_machines-activerecord
for modelling state machines in ActiveRecord
kramdown
for rendering Markdownsidekiq
for background job processingpaperclip
for file attachmentsminitest
for testingUse the default configuration files in /rails
for new projects.
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
Use the new syntax for validations
1
2
3
4
5
# Bad
validates_presence_of :title
# Good
validates :title, presence: true
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
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
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.
Mailer
suffix (i.e. CommentMailer
).&.
over try
.ActiveSupport
s.config/application.rb
.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 %>
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