-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathwizard.rb
More file actions
357 lines (313 loc) · 10.5 KB
/
Copy pathwizard.rb
File metadata and controls
357 lines (313 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
require 'active_support/all'
require 'active_model'
module DfE
# Multi-step form wizard framework using graph-based navigation
#
# DfE::Wizard provides a flexible, composable system for building multi-step wizards
# with conditional branching, state persistence, and validation.
#
# ## Architecture
#
# The framework is organized into distinct modules:
#
# - **Core**: Wizard capabilities (step management, navigation, validation, check-your-answers)
# - **Validators**: Validation logic for steps and paths
# - **StateStore**: Persistence adapters (session, redis, memory, etc.)
# - **StepsProcessor**: Flow implementations (graph-based navigation)
# - **RouteStrategy**: URL generation strategies
#
# ## Quick Start
#
# class MyWizard
# include DfE::Wizard
#
# def steps_processor
# DfE::Wizard::StepsProcessor::Graph.draw(self, predicate_caller: state_store) do |graph|
# graph.add_node :name, NameStep
# graph.add_node :email, EmailStep
# graph.add_node :review, ReviewStep
#
# graph.root :name
# graph.add_edge from: :name, to: :email
# graph.add_edge from: :email, to: :review
#
# graph.before_next_step { handle_return_to_check_your_answers(:review) }
# graph.before_previous_step { handle_back_to_check_your_answers(:review) }
# end
# end
#
# def route_strategy
# DfE::Wizard::RouteStrategy::NamedRoutes.new(namespace: 'my-wizard')
# end
# end
#
# ## Usage
#
# wizard = MyWizard.new(
# current_step: :email,
# state_store: DfE::Wizard::StateStore::SessionStore.new(session),
# current_step_params: params
# )
#
# wizard.next_step # => :review
# wizard.path_valid_to?(:review) # => true/false
# wizard.save
#
# @since 2.0.0
module Wizard
# @!group Core classes
#
# Provides the main contract on wizard objects
#
module Behaviours
# Check-your-answers pattern support
# @api public
autoload :CheckYourAnswers, 'dfe/wizard/behaviours/check_your_answers'
# Navigation through wizard steps
# @api public
autoload :Navigation, 'dfe/wizard/behaviours/navigation'
# All state management under wizard
#
# @api public
autoload :StateManagement, 'dfe/wizard/behaviours/state_management'
# Step lifecycle management
# @api public
autoload :StepManagement, 'dfe/wizard/behaviours/step_management'
# Step and path validation
# @api public
autoload :Validation, 'dfe/wizard/behaviours/validation'
end
# @!endgroup
# @!group Core classes
# Core wizard capabilities - provides the main objects
#
# @api public
module Core
# Full metadata of the wizard
# @api public
autoload :Metadata, 'dfe/wizard/core/metadata'
# Virtual step for redirecting steps
#
autoload :Redirect, 'dfe/wizard/core/redirect'
# Define a State store to manage all data and business logic
#
# @api public
autoload :StateStore, 'dfe/wizard/core/state_store'
# Define a step with attributes and validation
# @api public
autoload :Step, 'dfe/wizard/core/step'
end
# @!endgroup
# Module for building Check Your Answers pages
# @api public
autoload :CheckAnswersPresenter, 'dfe/wizard/check_answers_presenter'
# @!group Auto generate Documentation for any wizard
module Documentation
autoload :Generator, 'dfe/wizard/documentation/generator'
module Formatters
# Generate the documentation in markdown
autoload :MarkdownFormatter, 'dfe/wizard/documentation/formatters/markdown_formatter'
end
end
# @!endgroup
# @!group Default operations in every step of a wizard
#
# Provide default operations
#
module Operations
autoload :Validate, 'dfe/wizard/operations/validate'
autoload :Persist, 'dfe/wizard/operations/persist'
end
# @!endgroup
# @!group State Persistence
# Repository adapters for wizard state persistence
#
# All repositories implement a common, minimal interface.
# Can be extended with new adapters for different backends.
#
# @api public
module Repository
autoload :Base, 'dfe/wizard/repository/base'
autoload :Cache, 'dfe/wizard/repository/cache'
autoload :InMemory, 'dfe/wizard/repository/in_memory'
autoload :Model, 'dfe/wizard/repository/model'
autoload :Redis, 'dfe/wizard/repository/redis'
autoload :Session, 'dfe/wizard/repository/session'
autoload :WizardState, 'dfe/wizard/repository/wizard_state'
end
# @!endgroup
module Tooling
# Documentation capabilities
# @api public
autoload :DocManagement, 'dfe/wizard/tooling/doc_management'
# Inspect class to understand the whole wizard management in development
# @api public
autoload :Inspect, 'dfe/wizard/tooling/inspect'
# Logging capabilities
# @api public
autoload :LogManagement, 'dfe/wizard/tooling/log_management'
end
# @!group Step Operators
#
# Step operator to handle action config at any step of the wizard
#
module StepsOperator
autoload :Builder, 'dfe/wizard/steps_operator/builder'
end
# @!endgroup
# @!group Step Processors
# Step processors handle wizard flow and navigation
#
# Determines the next/previous step based on wizard state.
# Can be extended with new flow implementations.
#
# @api public
module StepsProcessor
# Base step processor
# @api public
autoload :Base, 'dfe/wizard/steps_processor/base'
# Graph-based step processor
# @api public
autoload :Graph, 'dfe/wizard/steps_processor/graph'
# Linear-based step processor
# @api public
autoload :Linear, 'dfe/wizard/steps_processor/linear'
end
# @!endgroup
# @!group Route Strategies
# Route strategy adapters for URL generation
#
# Generates URLs for wizard steps.
# Can be extended with new strategies for different routing patterns.
#
# @api public
module RouteStrategy
# Named routes strategy using Rails routes
# @api public
autoload :NamedRoutes, 'dfe/wizard/route_strategy/named_routes'
# Dynamic routes strategy with custom path builder
# @api public
autoload :DynamicRoutes, 'dfe/wizard/route_strategy/dynamic_routes'
# Configurable route strategy with DSL
# @api public
autoload :ConfigurableRoutes, 'dfe/wizard/route_strategy/configurable_routes'
end
module Logging
autoload :Logger, 'dfe/wizard/logging/logger'
autoload :NullLogger, 'dfe/wizard/logging/null_logger'
end
module Test
autoload :RSpecMatchers, 'dfe/wizard/test/r_spec_matchers'
end
# @!endgroup
# @!group Supporting Classes
autoload :Version, 'dfe/wizard/version'
# @!endgroup
# @!group Main API
include Core
include Tooling
include Tooling::LogManagement
include Tooling::DocManagement
include Logging
include Behaviours::Navigation
include Behaviours::Validation
include Behaviours::StepManagement
include Behaviours::StateManagement
include Behaviours::CheckYourAnswers
# Initializes a new wizard instance
#
# @param current_step [Symbol, nil] Name of the current step
# @param current_step_params [Hash] Parameters for the current step (typically from request)
# @param state_store [DfE::Wizard::StateStore] State persistence adapter
#
# @example
# wizard = MyWizard.new(
# current_step: :email,
# current_step_params: params,
# state_store: MyStateStore.new(
# repository: DfE::Wizard::Repository::InMemory.new,
# ),
# )
#
# @return [self]
def initialize(state_store:, current_step: nil, current_step_params: {})
@current_step_name = current_step&.to_sym
@current_step_params = current_step_params
@state_store = state_store
after_initialize
end
# Callback hook executed after wizard initialization
#
# Automatically triggers attribute method generation on the state_store
# if enabled via `define_step_attributes_methods?`.
#
# This ensures all step attribute reader methods are available immediately
# after wizard instantiation without requiring manual setup.
#
# @return [void]
#
# @example Automatic generation during initialization
# wizard = DBSCheckWizard.new(
# current_step: :personal_details,
# state_store: state_store
# )
#
# # Methods already available thanks to after_initialize
# state_store.first_name # => "Sarah"
# state_store.email # => "sarah@example.com"
#
# @note Called automatically by initialize - no manual invocation needed
# @note Can be overridden in subclasses to customize initialization behavior
#
# @api public
def after_initialize
define_step_attributes_in_state_store
end
def define_step_attributes_in_state_store
@state_store.step_definitions = step_definitions
@state_store.attribute_names = attribute_names
end
# The current step being displayed
# @return [Symbol, nil]
attr_writer :current_step_name
# The current step params - user answers
attr_writer :current_step_params
# The state store instance
# @return [DfE::Wizard::StateStore::Base]
attr_accessor :state_store
# @!endgroup
# @!group Extension Points
# Returns the steps processor for this wizard
#
# Must be implemented by subclasses.
#
# @return [DfE::Wizard::StepsProcessor::Graph]
# @raise [NotImplementedError]
#
# @example
# def steps_processor
# DfE::Wizard::StepsProcessor::Graph.draw(self, predicate_caller: state_store) do |graph|
# graph.add_node :step1, Step1
# graph.root :step1
# end
# end
def steps_processor
raise NotImplementedError, 'Subclass must implement #steps_processor'
end
# Returns the route strategy for this wizard
#
# Must be implemented by subclasses.
#
# @return [DfE::Wizard::RouteStrategy::NamedRoutes, DfE::Wizard::RouteStrategy::DynamicRoutes]
# @raise [NotImplementedError]
#
# @example
# def route_strategy
# DfE::Wizard::RouteStrategy::NamedRoutes.new(namespace: 'my-wizard')
# end
def route_strategy
raise NotImplementedError, 'Subclass must implement #route_strategy'
end
# @!endgroup
end
end