Skip to content

Instantly share code, notes, and snippets.

@jebw
Last active April 28, 2020 15:45
Show Gist options
  • Save jebw/c156a092dec8d2fac4a6ec4be4b005a3 to your computer and use it in GitHub Desktop.
Save jebw/c156a092dec8d2fac4a6ec4be4b005a3 to your computer and use it in GitHub Desktop.
Rails.application.routes.draw do
resources :registration_steps, only: %i(edit update)
end
class Step
attr_reader :name, :attributes, :condition
def initialize(name, attributes, &condition)
@name, @attributes = name, attributes
@condition = condition || true
end
end
class Journey
STEPS = []
class << self
def get(step_name)
raise UnknownStep unless STEPS[step_name]
STEPS[step_name]
end
def steps
STEPS.values
end
def step_names
STEPS.keys
end
private
def step(name, attributes, &condition)
STEPS[name] = Step.new(name, attributes, condition)
end
end
delegate :get, :steps, :step_names, to: :class
step :identity, %i(firstname lastname email)
step :contact_details, %i(address1 postcode country) do
firstname.present? || lastname.present? || email.present?
end
def new(step_name)
raise UnknownStep unless step_names.include?(step_name)
@step_name = step_name
end
def current
get(@step_name) || raise(UnknownStep)
end
def next_step(name = @step_name)
step = STEPS[steps.index_of(name) + 1]
return unless step
if instance_eval(step.condition)
step
else
next_step(step.name)
end
end
end
module RedisStore
include ActiveSupport::Concern
included do
delegate :redis, :key_for_id, to: :class
end
def save
Redis.current.set key, serialized_data
end
def key
self.id ||= SecureRandom.uuid
key_for_id(id)
end
module ClassMethods
def redis
Redis.current
end
def key_for_id(id)
"registration/#{id}"
end
def find(id)
serialized_data = redis.get key_for_id(id)
self.new deserialize_data(data)
end
end
end
class StepValidation
include ActiveSupport::Concern
def included
before_validation :clean_unneeded_fields
end
module ClassMethods
def validate_step(step_name, &block)
step = Journey.get(step_name)
with_options(on: [step.name, :complete], if: step.condition, &block)
end
def clean_unneeded_fields
Journey.steps.each do |step|
unless instance_eval(step.condition)
step.attributes.each { |attr| self.attr = nil }
end
end
end
end
end
class Registration
include ActiveModel::Model
include ActiveModel::Attributes
include RedisStore
include StepValidation
validate_step :identity do
validates :firstname, presence: true
validates :lastname, presence: true
validates :email, presence: true, email_format: true
end
validate_step :contact_details do
validates :address1, presence: true
validates :postcode, presence: true, format: { with: POSTCODE_FORMAT }
validates :country, presence: true, inclusion: { in: COUNTRY_CODES }
end
validate_step :t_and_cs do
validates :t_and_cs, presence: true, acceptance: true
end
end
class RegistrationStepsController < ApplicationController
def edit
@registration = current_registration
end
def update
@registration = current_registration
@registration.attributes = step_params
if @registration.valid?(on: current_step)
@registration.save!
redirect_to next_step_path
else
render 'edit'
end
end
private
def current_registration
if session[:registration_id]
@registration ||= Registration.find(session[:registration_id])
else
@registration ||= Registration.new
end
end
def current_step
@current_step ||= Journey.new(params[:id])
end
def step_params
params.require(:registration).permit(*current_step[:attributes])
end
def next_step_path
if next_step = current_step.next_step
registration_steps_path current_step.next_step
else
finished_path
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment