Last active
August 27, 2025 00:46
-
-
Save bensheldon/bbab8298f1b8b940efe8cb4c0c18c618 to your computer and use it in GitHub Desktop.
base_
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
| class BaseController < ApplicationController | |
| include Flowable | |
| self.form_flow = [ | |
| "AppliesController", | |
| # Initial "can we help you" questions | |
| "Apply::LocationsController", | |
| "Apply::RecentlyAppliedsController", | |
| "Apply::LanguagesController", | |
| # Applicant and Household | |
| "Apply::ApplicantsController", | |
| "Apply::ExperiencesController", | |
| "Apply::HouseholdsController", | |
| "Apply::DisabilitiesController", | |
| "Apply::StudentsController", | |
| "Apply::CitizenshipsController", | |
| "Apply::GrossIncomesController", | |
| # Result as part of Gross Income question | |
| "Apply::PhonesController", | |
| # Income and Expense questions | |
| "Apply::JobsController", | |
| "Apply::OtherIncomesController", | |
| "Apply::HousingExpensesController", | |
| "Apply::UtilitiesController", | |
| "Apply::MedicalExpensesController", | |
| "Apply::CareExpensesController", | |
| "Apply::ExpeditedServicesController", | |
| # Identity Questions | |
| "Apply::MembersController", | |
| "Apply::AddressesController", | |
| # Document Questions | |
| "Apply::CoveringExpensesController", | |
| "Apply::ReferralsController", | |
| "Apply::DocumentsController", | |
| # Submission Questions | |
| "Apply::DisabilitySupportsController", | |
| "Apply::LegalsController", | |
| "Apply::SignaturesController", | |
| "Apply::DonesController", | |
| ].freeze | |
| self.form_flow_steps = [ | |
| "AppliesController", # Step 1 | |
| "Apply::PhonesController", # Step 2 | |
| "Apply::DocumentsController", # Step 3 | |
| "Apply::SignaturesController", # Step 4 (final step) | |
| ].freeze | |
| end |
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
| class ExampleController < BaseController | |
| flow_action { :edit } | |
| skip_if { current_snap_app&.calculator&.expedited_service_asset_limit.nil? } | |
| done_if { current_snap_app&.expedited_low_cash&.not_nil? } | |
| def edit | |
| @snap_app = current_snap_app | |
| end | |
| def update | |
| @snap_app = current_snap_app | |
| @snap_app.assign_attributes(form_params) | |
| if @snap_app.save(context: :expedited_low_cash) | |
| if @snap_app.expedited_reason.present? | |
| redirect_to({ action: :reason }, status: :see_other) | |
| else | |
| redirect_to flow_next_path, status: :see_other | |
| end | |
| else | |
| render :edit, status: :unprocessable_content | |
| end | |
| end | |
| def reason | |
| @snap_app = current_snap_app | |
| # renders a static page that has a link with flow_next_path to move forward | |
| end | |
| private | |
| def form_params | |
| params.expect( | |
| snap_app: [:expedited_low_cash] | |
| ) | |
| end | |
| end |
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
| # frozen_string_literal: true | |
| module Flowable | |
| extend ActiveSupport::Concern | |
| ALWAYS_DONE = Module.new | |
| InvalidDoneNavigationError = Class.new(StandardError) | |
| mattr_accessor :verify_done_on_next_path, default: false | |
| mattr_accessor :verify_not_done_on_next_controller, default: false | |
| included do | |
| cattr_accessor :form_flow, default: [] # list of controllers as Strings in the flow | |
| cattr_accessor :form_flow_steps, default: [] # specific controllers that are landmarks for displaying progress | |
| helper_method :flow_path, | |
| :flow_next_path, | |
| :flow_progress_step, | |
| :flow_progress_step_percent, | |
| :flow_progress_percent, | |
| :flow_progress_percent? | |
| end | |
| class_methods do | |
| def flow_action(value = nil, instance: nil, &block) | |
| if value || block | |
| @_flow_action = value.presence || block | |
| else | |
| flow_action_value(instance) | |
| end | |
| end | |
| def flow_action_value(instance) | |
| action = @_flow_action || :edit | |
| action.respond_to?(:call) ? instance.instance_exec(&action) : action | |
| end | |
| def done_if(callable = nil, &block) | |
| @_done_if = callable || block | |
| end | |
| def skip_if(callable = nil, &block) | |
| @_skip_if = callable || block | |
| end | |
| def done_in_flow?(instance) | |
| @_done_if ? instance.instance_exec(&@_done_if) : true | |
| end | |
| def skip_in_flow?(instance) | |
| @_skip_if ? instance.instance_exec(&@_skip_if) : false | |
| end | |
| # When a controller is not in the flow, have it pretend to be another controller | |
| # for the purposes of calculating progress | |
| def flow_alias=(klass) | |
| raise "alias #{klass} not in flow of #{form_flow}" unless klass.in?(form_flow) | |
| @_flow_alias = klass | |
| end | |
| def flow_progress_step_indexes | |
| form_flow_steps | |
| .map { |step| form_flow.index { |value| value == step } } | |
| .tap { |result| raise "Controller not in form flow steps" if result.any?(&:nil?) } | |
| end | |
| end | |
| def flow_next_path | |
| raise NotDoneNavigationError, "#{self.class} called `flow_next_path` but was not done" if Flowable.verify_done_on_next_path && !self.class.done_in_flow?(self) | |
| next_controller_class = nil | |
| next_index = flow_current_index! | |
| loop do | |
| next_index += 1 | |
| next_controller_const = Array(self.class.form_flow[next_index]) | |
| raise "At end of flow" if next_controller_const.empty? | |
| next_controller_class = next_controller_const.first.constantize | |
| next if next_controller_class.skip_in_flow?(self) | |
| break | |
| end | |
| if Flowable.verify_not_done_on_next_controller | |
| done = next_controller_class.done_in_flow?(self) | |
| raise InvalidDoneNavigationError, "#{next_controller_class.name} is flow_next_path but already done" if done && done != ALWAYS_DONE | |
| end | |
| next_controller_action = next_controller_class.flow_action_value(self) | |
| url_for(controller: File.join("/", next_controller_class.controller_path), action: next_controller_action, only_path: true) | |
| end | |
| # The current step based on `form_flow_steps` | |
| def flow_progress_step(klass = self.class) | |
| klass = klass.constantize if klass.is_a?(String) | |
| current_controller_index = self.class.form_flow.index { |value| value == klass.name } | |
| klass.flow_progress_step_indexes.index { |step_index| current_controller_index < step_index } || klass.flow_progress_step_indexes.size | |
| end | |
| # The progress between this step and the next step | |
| def flow_progress_step_percent(klass = self.class, step:) | |
| klass = klass.constantize if klass.is_a?(String) | |
| current_step = flow_progress_step(klass) | |
| if step < current_step | |
| 100.0 | |
| elsif step > current_step | |
| 0.0 | |
| else | |
| index = index_in_flow(klass) | |
| index_in_step = index - self.class.flow_progress_step_indexes[current_step - 1] | |
| total_in_step = self.class.flow_progress_step_indexes[current_step] - self.class.flow_progress_step_indexes[current_step - 1] | |
| (index_in_step.to_f / total_in_step) * 100 | |
| end | |
| end | |
| def flow_progress_percent? | |
| index_in_flow.present? | |
| end | |
| def flow_progress_percent | |
| (flow_current_index! + 1) / self.class.form_flow.size.to_f * 100 | |
| end | |
| def index_in_flow(klass = self.class) | |
| klass = klass.name if klass.is_a?(Class) | |
| self.class.form_flow.index { |value| Array(value).first == klass } | |
| end | |
| def flow_current_index | |
| index_in_flow(self.class) | |
| end | |
| def flow_path(klass, action = nil) | |
| index_in_flow(klass) || raise("Controller not in flow") | |
| klass = klass.constantize unless klass.is_a?(Class) | |
| url_for(controller: File.join("/", klass.controller_path), action: action || klass.flow_action_value(self), only_path: true) | |
| end | |
| def flow_current_index! | |
| index_in_flow || raise("Controller not in flow") | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment