Skip to content

Instantly share code, notes, and snippets.

@jebw
Last active May 12, 2020 15:08
Show Gist options
  • Save jebw/c8409c2e70815f07cc5631a148525a77 to your computer and use it in GitHub Desktop.
Save jebw/c8409c2e70815f07cc5631a148525a77 to your computer and use it in GitHub Desktop.
# Just an abstracted hash data store
# For now can be backed by session data, later on maybe redis
class Store
attr_reader :data
delegate :[], to: :data
def initialize(data)
@data = data
end
def fetch(*keys)
data.slice Array.wrap(*keys)
end
end
## Parent class for Step
## Steps know about their parent(s) - think the Git Commit data structure
## Can determine if a step follows another step by checking if the step is in the 'current steps' parents list
class Step
include ActiveModel::Model
include ActiveModel::Attributes
class << self
def step_key
model_name.to_param
end
end
def initialize(store)
@store = store
assign_attributes_from_store
end
def assign_attributes_from_store
assign_attributes store.fetch(attribute_names)
end
def active?
true
end
def inactive?
!active?
end
def valid_if_active?
inactive? || valid?
end
## Already has access to the store, so save its attributes into the store
def save
return false unless active? && valid?
# do something to record attributes in @store
end
def follows?(step)
parents.include?(step) && active?
end
end
## Defines all the steps and has methods for moving between steps
## and determining the current position based off data in the store
class Journey
STEPS = [
Identity,
Address
].index_by(&:step_key)
def initialize(store)
@store
end
def step(step_name)
STEPS[step_name].new @store
end
def next_step(current_step)
current_step = STEPS[current_step] if current_step.is_a?(Symbol)
all_steps.find do |step|
step.follows?(current_step)
end
end
def calculate_next_step
# More convoluted walking through data we have
# For each step
# Is it active
# is it invalid?
# end
# end
end
def all_steps_valid?
all_steps.all?(:valid_if_active?)
end
def all_steps
STEPS.keys.map(&method(:step))
end
end
## Models, PARENTS array is just used for controlled the DAG
class Identity < Step
PARENTS = [].freeze
attribute :firstname
attribute :lastname
attribute :email
attribute :date_of_birth, :date
validates :firstname, presence: true
validates :lastname, presence: true
validates :email, presence: true, format: /@/
validates :date_of_birth, presence: true
end
class Address < Step
PARENTS = [Identity].freeze
attribute :line1
attribute :line2
attribute :line3
attribute :postcode
validates :line1, presence: true
validates :line2, presence: true
validates :postcode, presence: true, format: POSTCODE_FORMAT_REGEX
## Models determine themselves whether they are the current journey
## Actual logic is code so can be anything but assumption is it'll check
## values in the store from earlier steps
def active?
store[:firstname].present? || store[:lastname].present? || store[:email].present?
end
end
Rails.application.routes.draw do
namespace :apply do
## Single route, single controller
## Determines model to use from the params[:id] => that has the steps param key in
resources :steps, only: [:show, :update]
end
end
## Single controller, basic RESTful
## Step model used determined from params[:id]
## Some funk in private methods for setting up Store and Journey classes
class StepsController do
before_action :check_step_is_active
def show; end
def update
current_step.assign_attributes attribute_params
if current_step.save
redirect_to_next_step
else
render :show
end
end
private
def store
Store.new applydata
end
def applydata
session[:applydata] ||= {}
end
def current_step
journey.step params[:id]
end
helper_method :current_step
def journey
Journey.new store
end
def check_step_is_active
return true if current_step.active?
redirect_to_correct_step
end
def redirect_to_next_step
redirect_to step_path journey.next_step(current_step)
end
def redirect_to_correct_step
redirect_to step_path journey.calculate_next_step
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment