Rails Best Practices

General

Rails

  • Use devise to handle authentication.
  • Use Pry instead of irb.
  • Use Time or Date methods with zone, eg Time.current or Time.zone.now instead of Time.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 of db/schema.rb when using database features that cannot be represented by db/schema.rb file (such as triggers, functions).
  • Add db/schema.rb or db/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 over DateTime (unless you need to deal with dates and times in a historical context, in which case DateTime 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 } and Time.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 of Time.now.
    • Use Date.current instead of Date.today.
    • Use Time.zone.local(2017, 1, 1, 10, 30) instead of Time.new(2017, 1, 1, 10, 30).
    • Use Time.zone.at(time) instead of Time.at(time).
    • Use Time.zone.parse('2017-01-01') instead of Time.parse('2017-01-01').
  • 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 set config.time_zone = 'UTC').
    • 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)).