- Ruby 3.2.2
- Rails 7.0.4
- PostgreSQL
- Tailwindcss for CSS: perfect tool to quickly add simple design to an MVP
- Rspec for testing: the go-to testing library for Rails
- Simple Form for HTML forms: awesome tool to easily render forms with model data and error binding
- Faraday for HTTP client: gives a simple and easy interface to make HTTP requests to consume Apis
- Docker for containerization
Make sure you have Docker Engine installed on your local machine, then follow this steps to setup the app:
git clone git@github.com:renodor/rc_pro_quotes.git && cd rc_pro_quotes
: clone this repo to your local machine and navigate to the repo directoryecho 'RAILS_MASTER_KEY={{rails_master_key}}' > .env
: create an.env
file with theRAILS_MASTER_KEY
environment variable. (Replace{{rails_master_key}}
by the key given to you by email)docker compose build && docker compose run web rails db:prepare
: build Docker images and prepare Rails database. This may take a few minutes to download Docker images, install dependencies and gemsdocker compose up
: start Docker containers and access the app onlocalhost:3000
- (
docker compose down
: stop Docker containers)
You can also run any Rails commands with docker compose run web {{rails command}}
. Ex:
docker compose run web rails c
docker compose run web rails db:reset
- etc...
The app uses Rails credentials to store secrets.
In order to work properly the app needs to read the credentials stored in config/credentials.yml.enc
.
For that please make sure that your repo has an .env
file with the valid RAILS_MASTER_KEY
variable in it. (This should be ok if you followed step 2 of setup process)
Run Rspec specs with docker compose run web bundle exec rspec
The goals of the RC Pro app are:
- to collect commercial leads
- to propose RC Pro quotes to users (generated thanks to an Insurance Api)
Design-wise we then decided to have:
- a
Lead
model, to store leads data - a
Quote
model, to store quotes data - an
InsuranceApi
service, to consume the Insurance Api
- Stores contact informations of potential customers
- Leads are meant to be re-contacted by the commercial team in order to be converted to customers
- Every
lead
has at least anemail
aphone_number
and somenacebel_codes
(NACE-BEL codes) - Every
lead
also has astatus
(initial
,quoted
,contacted
,customer
,closed
) in order to follow along its commercial journey lead
can have some associated quotes
- Stores insurance quotes requested by leads
- Every
quote
belongs to a specificlead
- To create a
quote
we get some user information (nacebel_codes
,annual_revenue
,enterprise_number
,legal_name
,person_type
,coverage_ceiling_formula
,deductible_formula
), and then call the Insurance API to get thecoverage_ceiling
,deductible
andcovers
prices
- The
InsuranceApi::V1::Client
service is used to call the Insurance Api:https://staging-gtw.seraphin.be/quotes/
- Currently only used to call the
professional-liability
endpoint
The current version of the App has 3 pages with the following user journey:
- Home page: A form to get lead contact informations. With a CTA to go to quote form.
- Quote form: A form to get quote information, and advice user on the best options regarding his/her profile. With a CTA to create quote.
- Quote page: displays newly created RC quote.
The current version of the app has two models: Lead
and Quote
, and one InsuranceApi
service to call the Insurance Api. At first, the idea was simple:
- Attributes related to the user profile and that don't have an influence on the quote should go into the
Lead
model (things likeemail
,phone_number
,first_name
,last_name
,address
etc..) - Attributes that have an influence on the quote, that are part of the quote itself or needed to call the Insurance Api should go into the
Quote
model (things likeannual_revenue
,coverage_ceiling
,deductible
etc...)
But then the distinction appeared not so obvious:
enterprise_number
andlegal_name
are more related to the user profile, and shouldn't influence the quote in any ways, but are needed to call the Insurance Api and generate a quotenacebel_codes
(NACE-BEL codes) andperson_type
(natural person or not), can influence the quote but can also be considered as "user profile" attributes
So where to store those attributes? On the Lead
or Quote
model?
Overall the decision taken was to stay as close as possible to the Insurance Api: all attributes needed to consume the Api will go in the Quote
model. Considering that this model stores quotes generated by this Api.
This also helps to reduce the size of the lead creation form (the app home page), which makes it more digest and helps to generate more leads. (Which is the underlying main goal of the app).
The only exception is the nacebel_codes
attribute: it belongs to Lead
model, whereas it is needed to consume the Insurance Api. This is because we need those NACE-BEL codes to define the user activity
to then generate advises on the quote form (what deductible formula, coverage ceiling formula and covers is best for his/her profile). So it was way easier to collect this information before reaching the quote form page.
Also even if we want to stay close to the Insurance Api, the Quote
model still reflects a quote created for a specific lead and does not store necessarily all the attributes needed to consume the Api. For example:
coverage_ceiling_formula
anddeductible_formula
are collected on the quote form page, but are not stored in theQuote
model. Those attributes are used to consume the Api but then only the realcoverage_ceiling
anddeductible
values are stored in theQuote
model- Only the
covers
requested by the user are stored in theQuote
record
All these design decisions lead to some unusual params
manipulation in the QuotesController
in order to call the Insurance Api with the correct body and to create the Quote
record with the correct attributes:
- We need to get
nacebel_codes
from theLead
record - We need to get
coverage_ceiling_formula
anddeductible_formula
from the form params, but don't store it in thequote
record - We need to remove from the
InsuranceApi
payload thecovers
not choosen by the user
Because this is still a simple app with limited information, we decided to store some data directly as constant, enums or attributes. Going further this data could be extracted for better scalability. For example:
Lead::MEDICAL_NACEBEL_CODES
,Lead.activities
enum, andLead::PROFESSION_BY_NACEBEL_CODE
: are used to store theLead
recordactivity
and to display the professions on the lead form. This will be hard to maintain and scall with hundreds of activities and NACE-BEL codes. We could imagine a newActivity
model with a "many to many" relation with theLead
model, eachactivity
record having manynacebel_codes
as an array attributes or even it's ownNacebelCode
model etc...covers
is ajsonb
attribute in theQuote
model. It could be extracted in its ownCover
model, with a one to many relation withQuote
- The cover advice logic is also computed thanks to
Quote::COVERS_BY_ACTIVITY
,Quote::DEDUCTIBLE_FORMULA_BY_ACTIVITY
,Quote::COVERAGE_CEILING_FORMULA_BY_ACTIVITY
. This is not scalable with hundreds of activities and more complexe advice logic, and could be extracted in it's own service
We currently only have model specs (to test Lead
and Quote
models), and service spec (to test InsuranceApi::V1::Client
). Going further we would need to create integration specs to test the user journey into the different pages.
The app is not i18n ready, all texts (views, validation errors, forms attributes etc...) being hard coded.
The Api errors and exception handling is basic and would need to be more robust in a real production-ready app. For example:
- If the
InsuranceApi::V1::Client
service raises an error we just advice the user to try again. We should first log this exception somewhere, react to it, maybe having a retry mechanism. - If the
InsuranceApi::V1::Client
is not successful we don't analyse and react to the error data returned. We just rely on ourQuote
model ActiveRecord validation errors, "hoping" it will reflect the Api errors...
We could also use a tool like Sentry for error monitoring.
In the QuotesController#create
method, we call the InsuranceApi
service before validating that quote_params
are correct. We could prevent unecessary Api calls by having a more complexe logic to first validate some params, and then call the Api
There are currently no authorization process in the app. Anyone can access the quote page of any lead, or even the quote form to create a quote for any lead.
The front end design of the app is really basic and obviously need to be improved.