Skip to main content
Last updated: 13 Aug 2024

Conventions for Rails applications

The majority of GOV.UK applications are built using Ruby on Rails and over the years we have created a lot them. Along the way we’ve used a variety of patterns and conventions. We aim for GOV.UK apps to be consistent in their implementation with differences in domain logic. However, it isn’t practical to evolve all apps at the same pace so there can be differences in approaches used.

This document serves as a guide to the current conventions and preferences. It is intended to serve as a guide for teams creating new applications and for teams iterating older ones. If you are creating a new application do also consult the guides on setting up a new Rails application and how to name an application.

Non-Rails GOV.UK applications may still benefit from these conventions. Consider applying them if they do not conflict with existing conventions in an adopted framework.

Our approach

GOV.UK’s use of Rails is intended to be consistent with the conventions of the framework itself and we intend to embrace developments and deprecations from the framework. Where GOV.UK apps differ from Rails conventions (such as using the RSpec testing framework) these are intended to be consistent with common industry practices.

Tooling choices

Use GOV.UK supporting Gems

GOV.UK have published a number of gems that help achieve common needs across Ruby / Rails applications:

  • gds-sso - integration with signon for user and application authentication
  • govuk_app_config - provides common configuration for applications such as logging, error handling and metric reporting
  • govuk_publishing_components - provides a framework for embedding common GOV.UK interface components and creating your own application specific ones
  • govuk_sidekiq - Provides common configuration for using sidekiq with GOV.UK infrastructure
  • govuk_test - Provides configuration and dependencies for headless browser testing
  • rubocop-govuk - Provides GOV.UK linting rules for Ruby, Rails, RSpec and Rake

Using these helps maintain consistency across the programme and enables common iterations to be pushed out. It is encouraged that gems are iterated or issues filed (example) when apps need to vary from conventions introduced by these gems.

Lint your code

We have documentation on the tools and conventions for linting GOV.UK Rails applications.

Gemfile organisation

Aim for your Gemfile to feel consistent, logical and concise. It is easy for these files to become confusing with arbitrary orderings and a sporadic approach to versioning (example). However, this can be avoided by following simple conventions.

The first gem in your Gemfile should be "rails" as the root dependency of the application. This should have the version of Rails specified as an absolute version number (for example "6.0.3.4" rather than relative "~> 6.0") which stops any other gems being able to alter the version of Rails used.

You should then declare the other gems your application needs to run in all environments, which should be followed by groups to specify gem dependencies for particular environments (typically development and test).

You should avoid specifying the version of a gem (known as pinning) unless you have a specific need to do so. Pinning a version is rarely necessary as the Gemfile.lock file already stores the particular version used and typically we intend to keep applications compatible with a current version of a gem. Avoiding this makes future maintainers of your Gemfile not need to consider whether a versioning choice was arbitrary or specific - it also makes for a file that is easier to read - example.

Should you have a need to specify a particular version of the gem (for example, to indicate lack of compatibility with a newer release) leave a comment to explain the particular version. This documents why we need to care about the gem version. For example:

gem "elasticsearch", "~> 6" # We need a 6.x release to interface with Elasticsearch 6

Data storage

For non-specialist database needs you should use PostgreSQL with ActiveRecord. The db/seeds.rb can be used to populate the database for development and test environments.

For key-value datastore access (such as queues or distributed caches) Redis should be used. Typically a Redis datastore will not be shared between applications, for queue-based communication between applications we have precedent for using RabbitMQ via the Bunny gem, and AWS’ AmazonMQ.

For file storage local to an application Amazon S3 is the preferred choice, where this needs to be associated with a database ActiveStorage should be used.

Background job processing

The preferred approach for background job processing is to use ActiveJob with Sidekiq with the govuk_sidekiq gem providing configuration. ActiveJob is preferred due to its closer integration of Rails components (such as ActionMailer).

Scheduled background jobs for applications should also make use of Sidekiq, where sidekiq-scheduler is the conventional choice to achieve this.

JavaScript package management

For Rails applications you should use the Yarn package manager for JavaScript packages. Yarn is preferable to using NPM as Rails integrates directly with Yarn, providing default tasks and automating some workflows.

Frontend assets

GOV.UK applications use the Rails asset pipeline for precompiling assets - we are currently prevented from broadly adopting a more modern approach, due to asset pipeline coupling in our components gem, but do aspire to retire asset pipeline and encourage decoupling wherever possible.

Sending emails

GOV.UK Notify is the preferred approach for sending email. The mail-notify gem is conventionally used to integrate Notify with ActionMailer. There is further documentation on how GOV.UK applications use Notify.

Testing utilities

The preferred framework for testing Rails applications is rspec-rails where we aim to adhere to the project’s conventions. rubocop-govuk provides a linting configuration for RSpec. For mocking and test doubles you should use the provided rspec-mocks framework.

When working with rspec-rails we prefer merging the spec_helper.rb and rails_helper.rb files into a single spec_helper.rb to avoid the complexities of two configuration files. We also choose to specify --require spec_helper in a .rspec file to avoid prefixing every test with require "spec_helper".

Common tools used with RSpec are:

Testing JavaScript code

The conventional testing framework for JavaScript files in GOV.UK Rails applications is Jasmine.

This was originally chosen due to the jasmine gem’s tight integration with the Rails asset pipeline. However that gem has since been deprecated and should no longer be used.

Instead, we use the NPM package jasmine-browser-runner to run Jasmine tests in a headless Chrome browser. This is now the recommended approach.

For an example of how to configure Jasmine in a GOV.UK Rails application, see trade-tariff/signon#1810.

Configuration

Embrace 12 factor conventions

GOV.UK Rails applications aim to follow 12 factor conventions. These should manifest in your Rails application with practices such as:

  • environmental configuration done by environment variables, for example using ENV["DATABASE_URL"] over hardcoding the production database configuration;
  • close parity between development and production environments, for example using PostgreSQL in all environments - as opposed to SQLite for development and PostgreSQL for production;
  • avoiding, where possible, the need for additional configuration on the serving machine, for example needing additional Nginx rules to serve requests.

Inject secrets with environment variables

The conventional place to store secrets for a GOV.UK Rails application is config/secrets.yml. All production secrets should be populated with an environment variable; for dev and test environments it’s preferred to leave a usable placeholder default if an actual secret isn’t needed (example).

We haven’t migrated to using the encrypted config/credentials.yml.enc introduced in Rails 5.2. This approach presents us with a few problems, most notably that we run our apps in Rails “production” environment in numerous places (integration, staging, production) and need different secrets for them.

Specify London as the timezone

Configure your Rails application to consider the local timezone to be London time, this can be done with config.time_zone = "London" in your config/application.rb. This allows the presentation of dates to users, and any time based logic, to automatically be in UK time.

Note, you shouldn’t change the default configuration for the ActiveRecord timezone (config.active_record.default_timezone). This should remain as UTC which keeps the database un-opinionated on timezone.

Use api_only mode for API projects

For applications that do not serve requests to a web browser you should configure the Rails application to be API only, this simplifies Rails by removing unnecessary functionality. If you need to add a web browser interface to an API application you should consider creating a distinct frontend application (for example: email-alert-frontend and email-alert-api).

Editing of microcopy by non-developers

When it is intended for non-developers to edit microcopy in an application it is conventional to use Rails Internationalization (I18n) to abstract the copy outside of the application code. We use this even when an application is only in English. These files are stored in /config/locales as per Rails convention. You should only use these files for accessing copy and not for any application configuration.

Error handling

GOV.UK Rails applications should anticipate failure and do so gracefully. This happens automatically, to a degree, on GOV.UK frontend applications but not on admin applications where there is more granular control over error responses. GOV.UK Rails applications should be configured to provide GOV.UK branded error responses. Content Publisher provides an example of implementing this with the GOV.UK admin layout.

Organising your code

Organising code in a Rails application is frequently a source of debate and confusion, particularly as to what code belongs in the /app directory and what belongs in the /lib directory. We aim to follow what appears to be the prevalent convention where:

  • /app is used for:
    • the default Rails components (such as controllers, models, views, helpers, jobs)
    • commonly used classes in the application that have a common purpose and interface (good example: interactors, consistent interface and usage; bad example: reporters, just a one-off class)
  • /lib is used for:
    • classes that don’t logically fit into an app directory
    • complex business logic for the application, (for example, link expansion in the Publishing API)
    • Rake tasks

GOV.UK Rails applications should avoid creating an /app/lib directory, as this causes confusion with the prevalent /lib directory.

Configuration for applications, typically YAML files, should live in the /config directory.

Documenting your decisions

It’s conventional to create a /docs directory in GOV.UK Rails applications to store application documentation (which is typically linked to from the readme).

For recording the reasoning behind architectural decisions it’s conventional to use architectural decision records in the /docs/adr directory (example).

Testing strategies

We aim for well tested Rails applications that provide a good degree of coverage and provide assurances that an application is operating correctly. We value test suites that are fast to run, easy to read and test things consistently at different abstractions (unit, integration and functional).

Feature/system testing

When testing Rails applications from the perspective of an end user it is conventional to use RSpec Rails’ feature specs (new applications should use the more modern equivalent: system specs) via govuk_test’s Capybara configuration.

GOV.UK have adopted the FutureLearn readable feature test approach to writing feature tests in RSpec (example). This offers a similar level of readability of a cucumber test, without the difficulties in identifying the code used to perform the test.

Testing controllers

Use request specs for testing controllers, this is the recommended approach to replace controller specs reflecting a direction introduced in Rails 5.

Useful reading

The RSpec Styleguide