Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAPT-1633 Form objects for reminders | CAPT-1634 Encapsulate pre/post form rendering/submission logic per-journey (Part 1) #2742

Merged
merged 8 commits into from
May 20, 2024

Conversation

your
Copy link
Contributor

@your your commented May 13, 2024

This PR is the first iteration of CAPT-1634 applied to an isolated controller that handles a mini-journey for reminders (ECP/LUPP) and converts the reminders slugs to form objects as per CAPT-1633. In a follow-up PR, I will be adding the new concern to the claims controller and iterate with any improvements or changes required.

How to review / my notes

  1. Review the FormSubmittable concern and associated request spec; I won't repeat the rationale here as it's explained extensively in various comments inside the concern; worth noting that:
  • It's a first iteration, designed with speed in mind; each GET/POST/PATCH request on a <path>/:slug will dynamically define all the pre-post rendering/submission callback methods for that slug only. I have considered defining them as singleton methods so we could tap into them in a more declarative way (i.e. passing a block or a method name and options), but I didn't feel like pursuing it yet (it would probably mean storing all the callbacks and options in a variable for all slugs in a single req-res cycle):
   # potential future iteration...

   after_form_save_success :retrieve_student_loan_details, on: :"personal-details"

   # ...vs current iteration

   def personal_details_after_form_save_success
     retrieve_student_loan_details
   end
  • I have changed the concern name so many times, but I really like FormSubmittable now as it helps define a specific trait of controllers that handle form-based page sequences
  • This approach can allow for cleaner code as long as developers keep the callback implementations readable and slim
  • Callbacks can be difficult to debug; so are the rails controller filters (*_action), and are necessary for complex request cycles. I have added logging when filters are triggered; see an example here:
tail -f log/development.log| grep callback
2024-05-13 20:34:15.434222 I [79219:puma srv tp 005] {request_id: d0d27584-edb4-428d-92fb-cf40c0472e2f} Journeys::AdditionalPaymentsForTeaching::RemindersController -- Executing callback #handle_form_submission
2024-05-13 20:34:15.434457 I [79219:puma srv tp 005] {request_id: d0d27584-edb4-428d-92fb-cf40c0472e2f} Journeys::AdditionalPaymentsForTeaching::RemindersController -- Executing callback #email_verification_after_form_save_failure
2024-05-13 20:34:42.736920 I [79219:puma srv tp 001] {request_id: eb06c4f0-40b3-44f4-90a1-d5ee838d25d8} Journeys::AdditionalPaymentsForTeaching::RemindersController -- Executing callback #email_verification_before_update
2024-05-13 20:34:42.739362 I [79219:puma srv tp 001] {request_id: eb06c4f0-40b3-44f4-90a1-d5ee838d25d8} Journeys::AdditionalPaymentsForTeaching::RemindersController -- Executing callback #handle_form_submission
2024-05-13 20:34:42.752033 I [79219:puma srv tp 001] {request_id: eb06c4f0-40b3-44f4-90a1-d5ee838d25d8} Journeys::AdditionalPaymentsForTeaching::RemindersController -- Executing callback #email_verification_after_form_save_success
  • The concern is not tight to the PageSequence logic (which should probably be revised anyway), and only expects slugs to be defined along with current_slug and next_slug, so it can be used with any sequence
  1. Look at the new form objects for the reminders
  • I have realised that our Form#model_name should probably be generic and not linked to any particular AR model. I'm using a generic "form" name for the newly converted reminders slugs, but it could be extended to all the other forms
  • I have tried to re-use existing form objects for the reminders slugs, but:
    • personal-details is quite different from our "shared" one, as in this case it only focuses on full_name and email_address. I don't think it's worth extracting validations and sharing, not in this PR
    • email-confirmation is similar to the "shared" one, but in the shared one the OTP generation timestamp is an attribute of the current_claim... I didn't want that to be an attribute of current_reminder, but rather a transient attribute for the form object, hence I have injected it into the session and proxied to the form object. A similar approach could be used for the shared one
  • I'm using a current_data_object wrapper as the claim: argument to initialising the form object; I think it's a good abstraction
  • Forms needed to be re-organised per controller and not just journey
  • I have realised that our reminders journey is always "open", even if a window is closed... addressed that as well.

https://dfedigital.atlassian.net/browse/CAPT-1633
https://dfedigital.atlassian.net/browse/CAPT-1634

Copy link

@your your force-pushed the CAPT-1633_CAPT-1634-pt1 branch from ce0f1e0 to 72fa075 Compare May 14, 2024 09:20
end

def inject_sent_one_time_password_at_into_the_form
params[:form]&.[]=(:sent_one_time_password_at, session[:sent_one_time_password_at])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we store the sent_one_time_password_at on the Reminder? Then we can access it in the form object which might make things a bit simpler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your question, and I remember seeing you explain why that was a good idea for the Claim model.

My thoughts are that storing sent_one_time_password_at, a transient attribute, directly on the Reminder model might introduce unnecessary coupling between the model and the form object. While with the existing approach there is coupling between the controller and the form object, this coupling is more acceptable and expected within the context of MVC architecture. Controllers (and the "state", e.g. in the session) are meant to interact with form objects to handle form submissions and orchestrate the flow of data within the application. Either way I don't feel too strong, and it might make sense to have a consistent approach, but I wouldn't consider it a blocker. Will have a think

Copy link
Contributor

@rjlynch rjlynch May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure sent_one_time_password_at is really transient 🤔 but I don't think it's too big a deal to keep it in the session 👍

@your your marked this pull request as ready for review May 14, 2024 11:44
@your your force-pushed the CAPT-1633_CAPT-1634-pt1 branch from 72fa075 to 8fa6931 Compare May 14, 2024 13:30
Copy link
Contributor

@slorek slorek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 I think this is awesome work, thank you.

I do understand there will initially be a bit of extra cognitive load during debugging, but once you understand the callback structure (which you have documented well) it might actually make it a lot easier since the concerns are more clearly separated.

This is not the same problem as callbacks which are inserted in non-obvious ways since there is a fixed structure which will be commonly used as it unlocks the ability to support multiple journeys.

your added 8 commits May 20, 2024 19:20
This is a further abstraction; as a future step, the `model_name` should probably be
more generic (e.g. using `Form`)
This is another step to avoid coupling a generic form object to a specific AR model.

As a reminder, we keep `#persisted?` so Rails can make an informed decision to use PATCH
instead of POST when submitting a form. By definition, our form objects are and will always be
persisted "somewhere" (most likely not the DB in the future)
In this commit:

- Organise forms by controller name, as slugs can be repeated on different controllers for the
   same journey (and map to a different form and view)
- Use a generic and more abstract "form" model name for the new forms (it should probably
   be done for all the forms, but the changes would pollute this commit)
- Add new reminders forms and unit tests
See comments in the code for more details
There should be a POST for every slug; default to the first one when not specified.

The `new_reminder` path should probably be a more generic `/reminder`.
They are now moved to the form objects.
@your your force-pushed the CAPT-1633_CAPT-1634-pt1 branch from 8fa6931 to 5ba8e23 Compare May 20, 2024 18:21
@your your merged commit fa16937 into master May 20, 2024
13 checks passed
@your your deleted the CAPT-1633_CAPT-1634-pt1 branch May 20, 2024 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants