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:
- webmock gem to stub HTTP requests - for stubbing communication between GOV.UK applications
- SimpleCov for monitoring code coverage;
- factory_bot_rails for providing fixtures/test data;
ActiveSupport::Testing::TimeHelpers
for time manipulation.
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.