Skip to content

Commit 6acb1b7

Browse files
committed
[WIP] Add conventions documentation
1 parent 83d9681 commit 6acb1b7

12 files changed

+364
-0
lines changed

guides/conventions.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Conventions
2+
3+
1. [Application Namespaces](conventions/namespaces.md)
4+
2. [Rails Conventions](conventions/rails.md)
5+
3. [File Structure](conventions/file-structure.md)

guides/conventions/file-structure.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Conventions](/guides/conventions.md) /
2+
3+
# File Structure
4+
5+
- [assets](file-structure/assets.md)
6+
- [components](file-structure/components.md)
7+
- [controllers](file-structure/controllers.md)
8+
- [forms](file-structure/forms.md)
9+
- [helpers](file-structure/helpers.md)
10+
- [models](file-structure/models.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# Assets
4+
5+
Images, videos, and stylesheets are stored in the `assets` directory.
6+
7+
```
8+
.
9+
└ app
10+
└─ assets
11+
├─ images
12+
└─ stylesheets
13+
├─ [namespace]
14+
│ └─ application.scss
15+
└─ application.scss
16+
```
17+
18+
The top-level `application.scss` is the shared stylesheet for all namespaces.
19+
20+
The `find/application.scss` stylesheet should be included into the `layouts/find/application.html.erb` layout alongside the top-level `application.scss` stylesheet.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# View Components
4+
5+
We use [View Components](https://viewcomponent.org/) to create reusable components in our Rails applications.
6+
7+
```
8+
.
9+
└ app
10+
└─ components
11+
├─ [namespace] (Namespace-specific components)
12+
│ ├─ [component_name].rb
13+
│ └─ [component_name].html.erb
14+
├─ [model_name] (Model-specific components)
15+
│ ├─ [component_name].rb
16+
│ └─ [component_name].html.erb
17+
├─ [component_name].rb
18+
└─ [component_name].html.erb
19+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# Controllers
4+
5+
We organise our controllers within our namespaces.
6+
7+
We should follow resourceful controller conventions.
8+
9+
```
10+
.
11+
└ app
12+
└─ controllers
13+
├─ [namespace] (Namespace-specific controllers)
14+
│ ├─ [controller_name]_controller.rb
15+
│ └─ application_controller.rb
16+
└─ application_controller.rb
17+
```
18+
19+
Each namespace has its own `ApplicationController` as a base for all controllers within that namespace.
20+
21+
```ruby
22+
# Top-level application controller.
23+
# Shared functionality across all namespaces.
24+
class ApplicationController < ActionController::Base
25+
include Pundit::Authorization
26+
end
27+
28+
# Find application controller.
29+
# Base controller for all Find controllers.
30+
class Find::ApplicationController < ApplicationController
31+
end
32+
33+
# The API application controller inherits from ActionController::API.
34+
# Base controller for all API controllers.
35+
class API::ApplicationController < ActionController::API
36+
end
37+
38+
# The base controller for all v1 API controllers.
39+
class API::V1::ApplicationController < API::ApplicationController
40+
end
41+
```
42+
43+
This allows us to set base functionality across groups of controllers.
44+
45+
The pattern described in [ADR#6 Controller Structure](/guides/adr/0006-controller-structure.md) details a scalable pattern to managing nested resourceful controllers.
46+
47+
```ruby
48+
class Find::RecruitmentCycles::CoursesController < Find::ApplicationController
49+
# GET /recruitment_cycles/:recruitment_cycle_id/courses
50+
def index
51+
@courses = recruitment_cycle.courses
52+
end
53+
54+
# GET /recruitment_cycles/:recruitment_cycle_id/courses/:id
55+
def show
56+
@course = recruitment_cycle.courses.find(params[:id])
57+
end
58+
59+
private
60+
61+
def recruitment_cycle
62+
@recruitment_cycle ||= RecruitmentCycle.find(params[:recruitment_cycle_id])
63+
end
64+
end
65+
```
66+
67+
The `params[:id]` should always be the ID of the resource being acted upon. Parent resources should always be prefixed, e.g. `params[:recruitment_cycle_id]`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Conventions](/guides/conventions.md) /
2+
3+
# File Structure
4+
5+
- [assets](file-structure/assets.md)
6+
- [components](file-structure/components.md)
7+
- [controllers](file-structure/controllers.md)
8+
- [forms](file-structure/forms.md)
9+
- [helpers](file-structure/helpers.md)
10+
- [models](file-structure/models.md)
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# Form Objects
4+
5+
We use form objects to encapsulate the validation and parameter handling of forms in our application.
6+
7+
We organise our form objects within our namespaces and shared forms at the top-level.
8+
9+
```
10+
.
11+
└ app
12+
└─ forms
13+
├─ [namespace] (Namespace-specific forms)
14+
│ └─ [form_name]_form.rb
15+
└─ application_form.rb
16+
```
17+
18+
We have a single `ApplicationForm` base Form Object class. This class is used to set the foundations of shared logic across all form objects.
19+
20+
```ruby
21+
# Top-level application form.
22+
# Shared functionality across all form objects.
23+
class ApplicationForm
24+
include ActiveModel::Model
25+
end
26+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# View Helpers
4+
5+
View helpers are used to define presentational helpers, especially those that contain HTML. Many times, you'll find yourself needing to manipulate primitive data from models, in this case, put the presentational methods on the models themselves.
6+
7+
```
8+
.
9+
└ app
10+
└─ helpers
11+
├─ [namespace] (Namespace-specific forms)
12+
│ └─ [helper_name]_helper.rb
13+
└─ application_helper.rb
14+
```
15+
16+
There is no inheritance between helpers, so you can define a helper method in any helper file and use it in any view.
17+
18+
A helper file should be a collection of related helper methods. A helper file is structured around either a behavioural context or a resourceful context.
19+
20+
Below is an example of a behavioural helper file:
21+
22+
```ruby
23+
module LinkHelper
24+
def active_link_to(text, href)
25+
link_to text, user_path(user), class: class_names("active", current_page?(href))
26+
end
27+
end
28+
```
29+
30+
Below is an example of a resourceful helper file, these helpers should be nested within the namespace of the resource:
31+
32+
```ruby
33+
module CourseHelper
34+
def course_status_tag(course)
35+
govuk_tag(course.status, color: "green")
36+
end
37+
end
38+
```
39+
40+
All resourceful helpers should be prefixed with the resource name.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# Models
4+
5+
We follow standard Rails conventions for models. Models are stored in the `app/models` directory.
6+
7+
The `app/models` directory should contain only ActiveRecord models. Non-ActiveRecord PORO classes should be in the `app/lib` directory.
8+
9+
```
10+
.
11+
└ app
12+
└─ models
13+
├─ [namespace] (Namespace-specific models)
14+
│ └─ [model_name].rb
15+
└─ application_record.rb
16+
```
17+
18+
Models should contain the following logic:
19+
20+
- Associations
21+
- Validations
22+
- Scopes
23+
- Computed getter methods (Presentational methods)
24+
25+
We should refrain from adding business logic to models. Business logic belongs in service objects classes.
26+
27+
We should also refrain from adding callbacks to models. Callbacks are traditionally used to trigger side-effects and that logic belongs in service objects.
28+
29+
Additionally, we should be aware of all mutation points of models. By calculating computed values within service objects, we can test side-effects and coupled attributes in isolation.
30+
31+
We should limit scopes and define complex queries in query objects.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[Conventions](/guides/conventions.md) / [File Structure](/guides/conventions/file-structure.md) /
2+
3+
# Policies
4+
5+
We use [pundit](https://github.com/varvet/pundit) for authorization. Policies are stored in the `app/policies` directory.
6+
7+
```
8+
.
9+
└ app
10+
└─ policies
11+
├─ [namespace] (Service namespace-specific policies)
12+
│ ├─ [controller_namespace] (Nested resource-specific policies)
13+
│ │ └─ [model_name]_policy.rb
14+
│ └─ [model_name]_policy.rb
15+
└─ application_policy.rb
16+
```
17+
18+
To avoid a single policy taking on too much responsibility, we recommend creating separate policies for nested resources and service namespaces.
19+
20+
```ruby
21+
# Defines the actions that can be performed on a course by the current user within the Publish service.
22+
class Publish::CoursePolicy < ApplicationPolicy
23+
# GET /courses/new
24+
def new?
25+
# return a boolean
26+
end
27+
end
28+
29+
# Defines the actions that can be performed on a course of a specific provider by the current user within the Publish service.
30+
class Publish::Providers::CoursePolicy < ApplicationPolicy
31+
# GET /providers/:provider_id/courses/new
32+
def new?
33+
# return a boolean
34+
end
35+
end
36+
37+
# Defines the actions that can be performed on a course by the current user within the Support service.
38+
class Support::CoursePolicy < ApplicationPolicy
39+
# GET /courses/new
40+
def new?
41+
# return a boolean
42+
end
43+
end
44+
```

guides/conventions/namespaces.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[Conventions](/guides/conventions.md) /
2+
3+
# Namespaces
4+
5+
We use namespaces to organise our code into service-specific modules. This provides us with visibility of separation of concerns.
6+
7+
We benefit from this separtation in the following ways:
8+
9+
- Code under a namespace is intended to be used only by the service it belongs to.
10+
11+
We can easily detect when code is used incorrectly outside of its namespace.
12+
13+
This limits the blast radius of changes to a single service. For example, we can confidently make changes to a `Find` component and expect that it will only effect the "Find" service.
14+
15+
- Explicit shared code.
16+
17+
If code is shared, it is in the top-level namespace.
18+
19+
We can visually determine how much code is shared between services by looking at the non-namespaced files.
20+
21+
## Our namespaces
22+
23+
- `Find`
24+
- `Publish`
25+
- `Support`
26+
- `API`

guides/conventions/rails.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
[Conventions](/guides/conventions.md) /
2+
3+
# Rails Architecture
4+
5+
This document describes the Rails conventions of this application.
6+
7+
## 1. File Structure
8+
9+
Learn more about each of the directories' purposes in the [File Structure](conventions/file-structure.md) guide.
10+
11+
```
12+
.
13+
└ app
14+
├─ assets
15+
├─ components (View Components)
16+
├─ controllers
17+
├─ forms (Form Objects)
18+
├─ helpers
19+
├─ javascript
20+
├─ jobs
21+
├─ lib
22+
├─ mailers
23+
├─ models
24+
├─ policies
25+
├─ serializers
26+
├─ services (Service Objects)
27+
├─ validators
28+
├─ views
29+
└─ wizards
30+
```
31+
32+
The following directories are deprecated and should be removed:
33+
34+
- `app/decorators`
35+
36+
We should put decorator-type methods in models themselves.
37+
38+
- `app/view_objects`
39+
40+
We should use View Components (`app/components`) instead.
41+
42+
## 2. Environments
43+
44+
There are 2 kinds of environments we need to maintain:
45+
46+
1. Rails environment (`RAILS_ENV`)
47+
48+
There are 3 Rails environments:
49+
50+
- `production`
51+
- `test`
52+
- `development`
53+
54+
2. Application environment (`APP_ENV`) - This is commonly known as Hosting environment in other BAT applications.
55+
56+
This environment is used to determine the settings of the application.
57+
58+
There are 5 Application environments:
59+
60+
- `review`
61+
- `qa`
62+
- `staging`
63+
- `sandbox`
64+
- `production`
65+
66+
The `loadtest` and `rollover` environments are deprecated and should be removed. We can use `staging` temporarily for these use-cases.

0 commit comments

Comments
 (0)