Last active
August 1, 2019 12:48
-
-
Save baphled/3be4c3da65ae19742ff06d0789ca251e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Journeys | |
All of our services have one common theme, they require a user to complete, or at least start, a journey. | |
Journeys comprise of steps, a single step can have a page, a form or another step. | |
A page typically has some copy, some information for the user to consider. Where by a form contains validation and | |
fields for a user to fill in. | |
In this case we can learn from past projects and take what has worked with them in the past. | |
## A Journey | |
A journey is a path a user has to go through to complete a given task, i.e. Apply for a Fishing License, Waste | |
Exemption or another service. | |
As journeys can become quite complex it is crucial that we have a standardised method of providing these services. | |
A service can have a number of journeys and these journeys can change, not only depending on the users choices but, also | |
the type of user using the service. | |
To assist with a users journey they will be taken through a number of steps, each step can affect the journey and | |
require us to create a sequence of steps that make up the journey. | |
A step has a single form (allowing a user to provide information) or a page (providing the user with | |
information). We'll use a naming convention to allow us to focus on implementing the journey and not have to worry | |
about the moving parts. | |
In the past mapping steps to forms has worked well, but we've ended up always creating one or more maps which end up | |
becoming huge, and require comments to keep them organised. | |
We should be able to nest steps, allowing us to create complex paths in a more structured fashion. | |
`ruby | |
Journey.steps do |journey| | |
new_journey: [ | |
:personal_information, | |
:contact_details, | |
review_new_registration: [ | |
:review_details, | |
:declaration, | |
cancel_new_journey: if ->(object) { object.cancel_journey == true } | |
], | |
:complete_new_journey, | |
], | |
existing_journey: [ | |
find_step_current_step: [ | |
:journey_found, | |
:journey_not_found | |
] | |
], | |
renewal_journey: [ | |
:find_registration, | |
:email_renewal_information, | |
:show_renewal_details, | |
review_new_registration: [ | |
:declaration, | |
cancel_renewal: if ->(object) { object.change_required == true } | |
], | |
:confirmation | |
] | |
end | |
` | |
By mapping a journey is this way we can easily organise a collection of steps. | |
There are a number of benefits to this approach: - | |
* We can easily re-use steps. | |
* Re-ordering steps is as easy as re-ordering `Journey.steps`. | |
* Easier to reason about the logic required to initiate a step in the journey. | |
* Can get an understanding of the services steps, at a glance. | |
## Defining a Step | |
A step allows us to map a form, or a page, to a given step in the journey. Essentially being the glue that | |
binds individual steps into a complete journey. | |
To define a step we simply create a class with the corresponding name, the following example defines the `new_journey` | |
step. | |
`ruby | |
class NewJourneyStep < Journey::Step | |
def initialize(journey, user, options = {}) | |
self.journey = journey | |
self.user = user | |
self.page = NewJourneyPage.new(journey, user) | |
end | |
end | |
` | |
The above step declares that the `NewJourneyStep` step is a page. This page can be used to delegate any presentational | |
data to the appropriate class, allowing us to keep our models clean and purely focus on what the actual page requires. | |
`ruby | |
class PersonalInformationStep < Journey::Step | |
def initialize(journey, user, options = {}) | |
self.journey = journey | |
self.user = user | |
self.form = PersonalInformationForm.new(journey, user) | |
end | |
def new | |
respond_to do |format| | |
format.html { render :new } | |
end | |
end | |
def create | |
respond_to do |format| | |
if @form.submit(personal_information_params) | |
format.html { redirect_to @journey.next_step_path } | |
else | |
format.html { render :new } | |
end | |
end | |
end | |
protected | |
def personal_information_params | |
params | |
.require(:form) | |
.permit(:first_name, :last_name, :email, address: [:first_line, :second_line, :county, :post_code]) | |
end | |
end | |
` | |
## Implementing a Form | |
A form can take an object, using the same approach that waste-carriers-renewal takes, we can use a form object to | |
validate and send payloads to our data models. | |
The sole purpose of this form object is to validate and submit data to the appropriate model(s). | |
`ruby | |
class PersonalInformationForm < Journey::Form | |
attr_accessor :first_name | |
attr_accessor :last_name | |
attr_accessor :email | |
attr_accessor :address | |
validate_presence_of :first_name | |
validate_presence_of :last_name | |
validate_presence_of :email | |
def initialize(journey, user) | |
self.journey = journey | |
self.user = user | |
end | |
def submit(params) | |
self.address = Address.new(params[:address]) | |
super(params) | |
end | |
end | |
` | |
## Implementing a Page | |
A page will typically just provide some information for the user, this can comprise of a decorator or presenter to | |
assist with handling the dynamic information on the page. | |
` | |
class ReviewDetailsPage < Journey::Page | |
def initialize(journey, user) | |
self.journey = journey | |
self.user = user | |
end | |
def show | |
@project = ProjectPresenter.new(journey.project) | |
respond_to do |format| | |
format.html { render :review_details } | |
end | |
end | |
end | |
` | |
Above we define a page that responds to the `show` action, that displays the details we want a user to review. We | |
assign `@project` using a presenter specific for a users project. | |
Finally we render the view. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment