This app currently consists of a vessel submission API and a content management system. The API is intended to support on the Gov.UK hosted website as a means for the owner of a (singley owned) small ship to register their vessel under Part III of the UK Ships Register. The content management system is intended to be a means of managing the register and workload of officers of the Maritime and Coastguard Agency.
This is a standard Rails 5.2 web app with a PostgreSQL database.
Bootstrap has been used to theme the internal dashboard.
To set up a new development environment, follow the steps below:
bundle install
cp .env.example .env
cp config/database.yml.example config/database.yml
Then complete the .env
file with your environment settings, and complete the
config/database.yml
file with your database settings.
Setup your databases with:
bundle exec rake db:setup
bundle exec rake db:test:prepare
If capybara-webkit fails when you run bundle install
, refer to the capybara-webkit help page
To run the app locally:
bundle exec rails server
The app will then be available at http://localhost:3000/
We use RSpec for tests. To run the test suite, run:
bundle exec rspec
If you get the error DATABASE_URL environment variable is set
you will need to remove the DATABASE_URL
from the local .env file.
We are using the rack-cors gem.
CORS is configured in config/initalisers/cors.rb
and the origins are defined by ENV['CORS_ORIGINS']
.
Emails are created in the notifications table and delivered with delayed_job.
Until we have tested a full data set, it seemed wise to disable the sending of any broadcast emails.
To disable an email notification, override Notification#deliverable?
e.g.
def deliverable?
false
end
Automated reminder emails (system-generated) are generated daily. To defend against erroneous processes or bad data, they are created with an inital state of pending_approval
. Before these emails can be sent, they need to marked as approved
by a user. See the link Automated Email Queue
in the top right navigation.
Emails can be previewed at: /rails/mailers in development.
Files are uploaded with paperclip and store on Microsoft Azure. Ensure that the following credentials are set for each environment (except in :test, when the file storage is :filesystem).
ENV['AZURE_STORAGE_ACCOUNT']
ENV['AZURE_ACCESS_KEY']
ENV['AZURE_CONTAINER_NAME']
Refer to config/initializers/paperclip.rb
for implementation.
Note that Private URLs are enabled and the file url should be generated with the helper method: azure_private_asset_url
, found in app/helpers/asset_helper.rb
.
SMS Notifications are delivered by Gov.UK Notify.
This is implemented in app/services/sms_provider.rb
.
Ensure that the API key is set as ENV['NOTIFY_API_KEY']
,
And the Template ID as ENV['NOTIFY_TEMPLATE_ID']
.
Postcode lookups are performed by a Javascript API call to https://ideal-postcodes.co.uk. The account is managed by the MCA and the code uses Ideal Postcode's JQuery API.
Ensure that the API key is set as POSTCODE_LOOKUP_API_KEY
Cron jobs are managed with the whenever gem.
To ensure the crontab is kept up to date, ensure that whenever --update-crontab
is called on deployment.
Schedule: Daily
If the submission#referred_until
date has been reached, applications should be restored to the unclaimed tasks queue. To run this application_type:
rake waves:expire_referrals
The services that can be performed are stored in the services
table. Each service has:
- A name
- Service standard (standard_days or premium_days)
- Pricing for the part / service standard
- UI rules (e.g. validations, form display helpers)
- Activities (e.g. generate_new_5_year_registration, update_registry_details)
- Print templates (e.g. registration_certificate, cover_letter)
Submissions are requests to change something in the Registry of Ships. In the UI, we call them 'Applications' but in the Rails world, 'application' is a reserved word. A submission can be for a variety of different application types. The application type will determine whether a submission is for a registered vessel or a (flavour of) new registration.
The full list of application type can be retrieved from ApplicationType.all_
. Note that the ApplicationType class references the WavesUtilities gem. It is also viewable in the admin section at: http://localhost:3000/admin/application_types
.
Submissions have a state of active
(aka open
) or closed
. A submission can be closed when all tasks have been completed or cancelled.
A registered vessel can have one open
submission at a time.
There are three points of entry for a submission:
- Via a customer entry on the Online portal (submission#source
:online
) - Via Document Entry by a Reg Officer (submission#source
:manual_entry
) - Finance Team create Payments which are linked or converted to submissions by a Registration Officer.
When a submission enters the default :incomplete state, it fires an event build_default
, invoking the SubmissionBuilder
and setting up all the defaults. If you want to initialize a submission object to build a form or any other instance when you don't want a record to be created, you need to forecfully set the state to :initializing. For example: Submission.new(state: :initializing)
.
Submissions have many tasks (to be performed by a Reg Officer), and they travel through a state machine.
:initialising - Just added via the Task Manager, pending confirmation
:unclaimed - Confirmed by a Reg Officer. Target Dates have been assigned
:claimed - Add to a Reg Officer's Task Queue. Can be actionned.
:referred - Pending information from the customer.
:completed - Task is done. Activities have been performed. Print Jobs have been added to the Print Queue
:cancelled - Irreversible.
A task's behaviour depends on the Service, viewable within each part at: http://localhost:3000/admin/services/processes
- Rules - define the page templates to display and validations to be applied.
- Activities - describe what happens when the task is processed (
application_processor.rb
) - Print Templates - determine the printable outputs of a completed task
RSS require monitoring of staff performance, so actions such as completing or referring tasks are logged. There is a helper method available globally: log_work!
.
Work logs can be viewed at: http://localhost:3000/work_logs
The changes that the submission will perform are stored as JSON objects in submission#changeset. If the submission is associated with a registered vessel, the 'current information' will be stored as JSON objects in submission#changeset.
These JSON objects are mounted with the VirtualModel
class and exposed by the submission as: submission.vessel
, etc.
Payments come in two flavours and have a polymorphic association with the Payment
model.
- World Pay Payments (online)
- Finance Payments (manual_entry)
In the submission context, we store owner details in the declarations table. The changes requested are stored as JSON objects in declaration#changeset. One owner maps to one declaration, and a submission can have many declarations.
Note that, while a changeset may contain owners
, these are ignored once the Builder::SubmissionBuilder
has run and the declarations have been built.
The Registry consists of:
- Registrations. The record of the current and previous registration details for a vessel.
- Vessels. The current vessel record.
- Owners. The current record of vessel owners.
The app follows the standard Rails MVC pattern, additionally:
- Builders are used to perform actions that create or update database records.
- Decorators are used to add UI behaviour to the models.
- Policies are user to answer questions: 'Can the submission be approved now?'
- Services are used to encapsulate helper methods that don't make any database changes.
And mixins:
- Concerns. Tried to limit the use of Active Record Concerns to functionality that was open to all models.
- Submission mixins. The Submission class was getting too big for rubocop, so
app/model/submission/associations.rb
andapp/model/submission/state_machine.rb
were extracted to modules. No reason other than code organization. Would welcome smart refactoring.
Searching is helped along with pg_search gem.
To rebuild the (e.g. Submission) indexes after making changes to the configuration:
PgSearch::Multisearch.rebuild(Submission)
Global search configuration can be added to:
config/initializers/pg_search.rb
Adding attributes to the data model can be a fairly complex process as there are a number of moving parts to consider. In the example below, we want to record the number of shares_held
by an owner. If adding additional tables (e.g. groups of shareholders), be mindful that the table(s) will need to exist in both the submission
domain and the registry
.
- Add a column
shares_held
to thedeclarations
table. Note thatdeclarations
are the owners associated with a submission. - Build out the model, controller and views in Waves so that a reg officer can record the
shares_held
by an owner. - Add a column
shares_held
to thecustomers
table. Note that all entities associated with a registered vessel are sub-classed from this table. - Extend
Builder::RegistryBuilder
to add theshares_held
by an owner to thecustomers
table. - Extend
Register::Vessel#registry_info
to ensure that theshares_held
is captured.#registry_info
is used to store a snapshot of the registered vessel in theregistrations
table. For ourshares_held
example we don't need to make any changes toRegister::Vessel#registry_info
because all owner attributes are assigned by default:owners: owners.map(&:attributes)
- If the new data attribute will not be stored and edited in the
Submission#changeset
(i.e. it's not vessel_info), then extendBuilder::SubmissionBuilder
orBuilder::DeclarationBuilder
to enable the new attribute. For ourshares_held
example, we add the lineshares_held: owner[:shares_held].to_i
toBuilder::DeclarationBuilder#build_declarations
. - The
shares_held
name / value should also be added somewhere on the registered vessel page (app/views/vessels
).