Rails Best Practices
General
- Use safe navigation operator (
&
) instead of try. - Use the
.ruby-version
file to specify the Ruby version. - Require parent classes to raise
NotImplementedError
on empty methods which are implemented in child classes only.
Rails
- Use devise to handle authentication.
- Use Pry instead of
irb
. - Use Time or Date methods with zone, eg
Time.current
orTime.zone.now
instead ofTime.now
. - Avoid using concerns if possible. Use one of the design patterns instead. If necessary, only use concerns for code reusing among different classes.
Database
- Use
db/structure.sql
file instead ofdb/schema.rb
when using database features that cannot be represented bydb/schema.rb
file (such as triggers, functions). - Add
db/schema.rb
ordb/structure.sql
to Git. - Don’t change or delete a migration after it has been pushed to remote.
Branching the database along with the code
In most cases it’s a good idea to use a separate database for each feature branch. This approach will exclude schema changes made in other feature branches and prevent accidental changes from being pushed to production beforehand. Branching the database will also prevent issues caused by schema changes in other branches e.g. removing tables or columns.
Example database configuration:
development:
database: PROJECT-NAME_<%= `git rev-parse --abbrev-ref HEAD`.strip %>
test:
database: PROJECT-NAME_test_<%= `git rev-parse --abbrev-ref HEAD`.strip %>
After creating a new branch make sure to create a new database and seed or clone from another environment.
JavaScript / Rails UJS
A more sophisticated front-end architecture which we use in long-term projects is described on Front-End Architecture. However the project you’re working on might be simple enough to implement without client-side rendering.
Here’s a list of best practices to be used in that case:
- Avoid JavaScript in Rails views, unless it’s the best case scenario, or has something to do with ERB. View-rendered JavaScript prevents any transpiling, minimization or obfuscation.
- Wrap your page specific logic in anonymous functions to avoid global pollution.
- Avoid unnecessarily polluting the global namespace with your reusable code, instead, introduce an appropriate namespace.
- Split your JavaScript code appropriately, at least one file per controller.
- Prefer server-side response induced changes, instead of duplicating the logic e.g. if you had a tree, retrieve the tree state from the server on each request and adjust, instead of adding or removing tree nodes using JavaScript.
-
Prefix functionality related classes with ‘js’ to prevent functionality loss with design changes.
e.g.
<div class='btn js-open-modal'>Open modal!</div>
Views
- Avoid using local variables, consider extracting these local variables into presenters.
- Avoid using HAML or Slim for view templates, use ERB instead.
Time Zones
Typically when working on a Rails application you have four different time zones (system time, application time, user selected time and database time) that you have to deal with (although we have both system time and database time set to UTC). In order to make working with time zones easier, we use these practices:
- Prefer
Time
overDateTime
(unless you need to deal with dates and times in a historical context, in which caseDateTime
is preferred). - Your application should avoid system time because it might cause some unexpected behavior. For example,
Time.use_zone('Fiji') { Time.parse('2018-01-01').utc }
andTime.use_zone('Fiji') { Time.zone.parse('2018-01-01').utc }
will return completely different times. Therefore, you should always use application time instead: -
- Use
Time.current
instead ofTime.now
.
- Use
-
- Use
Date.current
instead ofDate.today
.
- Use
-
- Use
Time.zone.local(2017, 1, 1, 10, 30)
instead ofTime.new(2017, 1, 1, 10, 30)
.
- Use
-
- Use
Time.zone.at(time)
instead ofTime.at(time)
.
- Use
-
- Use
Time.zone.parse('2017-01-01')
instead ofTime.parse('2017-01-01')
.
- Use
- If your application allows each user to have a different time zone:
-
- Use UTC time zone as your application’s time zone (in
config/application.rb
setconfig.time_zone = 'UTC'
).
- Use UTC time zone as your application’s time zone (in
-
- Calculate time zone specific logic in application’s time zone.
-
- Apply user’s time zone only when the calculated time is about to be shown to a user.
-
- An exception to the rule above should be made if the calculation itself is dependant on a time zone. For example, you might need to select all the users that have a local time of 9:00.
- Use ISO8601 standard whenever possible.
- Keep in mind that whenever a time object is written to a database it is converted and stored in UTC and whenever it is read from a database it is converted to an application’s time zone. Therefore, you should always use ActiveRecord’s safe interpolation when querying a database (for example,
SomeModel.where('created_at > ?', 1.day.ago)
).