datashift_journey 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +391 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/stylesheets/datashift_journey/partials/_state_jumper_toolbar.scss.erb +27 -0
  6. data/app/controllers/concerns/datashift_journey/error_renderer.rb +9 -0
  7. data/app/controllers/concerns/datashift_journey/review_renderer.rb +21 -0
  8. data/app/controllers/concerns/datashift_journey/token_based_access.rb +23 -0
  9. data/app/controllers/concerns/datashift_journey/validate_state.rb +59 -0
  10. data/app/controllers/datashift_journey/abandon_enrollments_controller.rb +14 -0
  11. data/app/controllers/datashift_journey/abandonments_controller.rb +17 -0
  12. data/app/controllers/datashift_journey/api/v1/states_controller.rb +49 -0
  13. data/app/controllers/datashift_journey/application_controller.rb +53 -0
  14. data/app/controllers/datashift_journey/errors_controller.rb +24 -0
  15. data/app/controllers/datashift_journey/journey_ends_controller.rb +19 -0
  16. data/app/controllers/datashift_journey/journey_plans_controller.rb +166 -0
  17. data/app/controllers/datashift_journey/page_states_controller.rb +49 -0
  18. data/app/controllers/datashift_journey/reviews_controller.rb +32 -0
  19. data/app/controllers/datashift_journey/state_jumper_controller.rb +51 -0
  20. data/app/factories/datashift_journey/form_object_factory.rb +68 -0
  21. data/app/forms/datashift_journey/collector/base_collector_form.rb +60 -0
  22. data/app/forms/datashift_journey/concerns/form_mixin.rb +64 -0
  23. data/app/forms/datashift_journey/null_form.rb +20 -0
  24. data/app/helpers/datashift_journey/application_helper.rb +50 -0
  25. data/app/helpers/datashift_journey/back_link_helper.rb +9 -0
  26. data/app/models/datashift_journey/collector/data_node.rb +18 -0
  27. data/app/models/datashift_journey/collector/form_definition.rb +35 -0
  28. data/app/models/datashift_journey/collector/form_field.rb +61 -0
  29. data/app/models/datashift_journey/journey_review.rb +65 -0
  30. data/app/models/datashift_journey/review_data_section.rb +32 -0
  31. data/app/serializers/datashift_journey/collector/page_state_serializer.rb +9 -0
  32. data/app/serializers/state_machines/state/state_serializer.rb +5 -0
  33. data/app/views/datashift_journey/collector/_generic_form.html.erb +14 -0
  34. data/app/views/datashift_journey/errors/401.html.erb +13 -0
  35. data/app/views/datashift_journey/errors/403.html.erb +13 -0
  36. data/app/views/datashift_journey/errors/404.html.erb +13 -0
  37. data/app/views/datashift_journey/errors/422.html.erb +11 -0
  38. data/app/views/datashift_journey/errors/500.html.erb +13 -0
  39. data/app/views/datashift_journey/errors/503.html.erb +11 -0
  40. data/app/views/datashift_journey/errors/invalid_authenticity_token.html.erb +14 -0
  41. data/app/views/datashift_journey/journey_ends/new.html.erb +5 -0
  42. data/app/views/datashift_journey/journey_ends/show.html.erb +5 -0
  43. data/app/views/datashift_journey/journey_plans/_form.html.erb +14 -0
  44. data/app/views/datashift_journey/journey_plans/_render_fields.html.erb +24 -0
  45. data/app/views/datashift_journey/journey_plans/edit.html.erb +6 -0
  46. data/app/views/datashift_journey/journey_plans/new.html.erb +6 -0
  47. data/app/views/datashift_journey/shared/_default_actions.html.erb +6 -0
  48. data/app/views/datashift_journey/shared/_errors.html.erb +19 -0
  49. data/app/views/datashift_journey/shared/_submit_action.html.erb +5 -0
  50. data/app/views/datashift_journey/state_jumper/_toolbar.html.erb +16 -0
  51. data/config/brakeman.ignore +42 -0
  52. data/config/i18n-tasks.yml +103 -0
  53. data/config/initializers/exceptions_app.rb +3 -0
  54. data/config/initializers/mime_types.rb +1 -0
  55. data/config/initializers/rswag-api.rb +14 -0
  56. data/config/initializers/rswag-ui.rb +9 -0
  57. data/config/locales/en.yml +39 -0
  58. data/config/routes.rb +44 -0
  59. data/lib/datashift_journey/collector/field_snippet.rb +12 -0
  60. data/lib/datashift_journey/collector/page_state_snippet.rb +12 -0
  61. data/lib/datashift_journey/collector/snippet.rb +14 -0
  62. data/lib/datashift_journey/configuration.rb +103 -0
  63. data/lib/datashift_journey/engine.rb +38 -0
  64. data/lib/datashift_journey/exceptions.rb +26 -0
  65. data/lib/datashift_journey/helpers/back_link.rb +58 -0
  66. data/lib/datashift_journey/journey/machine_builder.rb +59 -0
  67. data/lib/datashift_journey/prepare_data_for_review.rb +219 -0
  68. data/lib/datashift_journey/reference_generator.rb +51 -0
  69. data/lib/datashift_journey/state_machines/branch_sequence_map.rb +35 -0
  70. data/lib/datashift_journey/state_machines/extensions.rb +40 -0
  71. data/lib/datashift_journey/state_machines/planner.rb +206 -0
  72. data/lib/datashift_journey/state_machines/sequence.rb +86 -0
  73. data/lib/datashift_journey/state_machines/state_machine_core_ext.rb +72 -0
  74. data/lib/datashift_journey/version.rb +3 -0
  75. data/lib/datashift_journey.rb +57 -0
  76. data/lib/generators/datashift_journey/collector/collector_generator.rb +43 -0
  77. data/lib/generators/datashift_journey/collector/install_collector_generator.rb +61 -0
  78. data/lib/generators/datashift_journey/collector/install_mongo_collector_generator.rb +44 -0
  79. data/lib/generators/datashift_journey/collector/templates/collector_concern.rb.tt +34 -0
  80. data/lib/generators/datashift_journey/collector/templates/collector_migration.rb.tt +46 -0
  81. data/lib/generators/datashift_journey/forms_generator.rb +45 -0
  82. data/lib/generators/datashift_journey/generate_common.rb +33 -0
  83. data/lib/generators/datashift_journey/setup/USAGE +12 -0
  84. data/lib/generators/datashift_journey/setup/setup_generator.rb +44 -0
  85. data/lib/generators/datashift_journey/setup/templates/initializer.rb.tt +17 -0
  86. data/lib/generators/datashift_journey/setup/templates/model_concern.rb.tt +37 -0
  87. data/lib/generators/datashift_journey/templates/base_form.rb.tt +7 -0
  88. data/lib/generators/datashift_journey/templates/collector_form.rb.tt +18 -0
  89. data/lib/generators/datashift_journey/templates/collector_view.rb.tt +15 -0
  90. data/lib/generators/datashift_journey/templates/journey_plan_form.rb.tt +23 -0
  91. data/lib/generators/datashift_journey/templates/journey_plan_view.rb.tt +15 -0
  92. data/lib/generators/datashift_journey/views_generator.rb +35 -0
  93. data/lib/tasks/state_machine.thor +48 -0
  94. data/spec/datashift_journey/complex_journey_spec.rb +132 -0
  95. data/spec/datashift_journey/machine_builder_spec.rb +268 -0
  96. data/spec/datashift_journey/planner_spec.rb +129 -0
  97. data/spec/datashift_journey/sequence_spec.rb +20 -0
  98. data/spec/dummy/Rakefile +6 -0
  99. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  100. data/spec/dummy/app/assets/stylesheets/application.css +16 -0
  101. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  102. data/spec/dummy/app/forms/base_form.rb +8 -0
  103. data/spec/dummy/app/forms/business_details_form.rb +17 -0
  104. data/spec/dummy/app/forms/business_type_form.rb +17 -0
  105. data/spec/dummy/app/forms/contact_details_form.rb +17 -0
  106. data/spec/dummy/app/forms/enter_reg_number_form.rb +17 -0
  107. data/spec/dummy/app/forms/new_or_renew_form.rb +17 -0
  108. data/spec/dummy/app/forms/postal_address_form.rb +17 -0
  109. data/spec/dummy/app/forms/question1_form.rb +9 -0
  110. data/spec/dummy/app/forms/question2_form.rb +12 -0
  111. data/spec/dummy/app/forms/sole_trader_name_form.rb +17 -0
  112. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  113. data/spec/dummy/app/models/payment.rb +5 -0
  114. data/spec/dummy/app/services/datashift_journey/models/collector_journey.rb +51 -0
  115. data/spec/dummy/app/views/_question1.html.erb +13 -0
  116. data/spec/dummy/app/views/_question2.html.erb +9 -0
  117. data/spec/dummy/app/views/layouts/alternative.html.erb +14 -0
  118. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  119. data/spec/dummy/app/views/pages/home.html.erb +6 -0
  120. data/spec/dummy/app/views/pages/start.html.erb +5 -0
  121. data/spec/dummy/bin/bundle +3 -0
  122. data/spec/dummy/bin/rails +4 -0
  123. data/spec/dummy/bin/rake +4 -0
  124. data/spec/dummy/bin/setup +29 -0
  125. data/spec/dummy/config/application.rb +39 -0
  126. data/spec/dummy/config/boot.rb +5 -0
  127. data/spec/dummy/config/database.yml +22 -0
  128. data/spec/dummy/config/environment.rb +5 -0
  129. data/spec/dummy/config/environments/development.rb +47 -0
  130. data/spec/dummy/config/environments/production.rb +79 -0
  131. data/spec/dummy/config/environments/test.rb +47 -0
  132. data/spec/dummy/config/initializers/assets.rb +11 -0
  133. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  134. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  135. data/spec/dummy/config/initializers/datashift_journey.rb +6 -0
  136. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  137. data/spec/dummy/config/initializers/inflections.rb +16 -0
  138. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  139. data/spec/dummy/config/initializers/session_store.rb +3 -0
  140. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  141. data/spec/dummy/config/locales/en.yml +27 -0
  142. data/spec/dummy/config/routes.rb +28 -0
  143. data/spec/dummy/config/secrets.yml +22 -0
  144. data/spec/dummy/config.ru +4 -0
  145. data/spec/dummy/db/migrate/20160101091218_create_dummy_checkout.rb +43 -0
  146. data/spec/dummy/db/migrate/20161221100703_datashift_journey_create_collector.rb +56 -0
  147. data/spec/dummy/db/schema.rb +142 -0
  148. data/spec/dummy/lib/version.rb +1 -0
  149. data/spec/factories/collector_factory.rb +16 -0
  150. data/spec/factories/collector_snippet_factory.rb +9 -0
  151. data/spec/factories/collector_state_page_factory.rb +12 -0
  152. data/spec/factories/data_node_factory.rb +9 -0
  153. data/spec/factories/form_factory.rb +6 -0
  154. data/spec/features/basic_navigation_spec.rb +125 -0
  155. data/spec/helpers/application_helper_spec.rb +40 -0
  156. data/spec/integration/collector/page_state_spec.rb +45 -0
  157. data/spec/models/collector/collector_spec.rb +100 -0
  158. data/spec/models/collector/page_state_spec.rb +30 -0
  159. data/spec/rails_helper.rb +73 -0
  160. data/spec/requests/collector/api/v1/page_state_spec.rb +85 -0
  161. data/spec/requests/collector/api/v1/states_spec.rb +28 -0
  162. data/spec/spec_helper.rb +63 -0
  163. data/spec/support/asserts.rb +27 -0
  164. data/spec/support/mailer_macros.rb +25 -0
  165. data/spec/support/page_objects/base_page_object.rb +77 -0
  166. data/spec/swagger_helper.rb +25 -0
  167. metadata +425 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2c69a2e39ef25608483ad7d2597251e7a3579c8b40cfb62c77bce84e51bceca9
4
+ data.tar.gz: 272adc891084777add171f204ce3f39f7b768ed34effde573288ca7e3939cc0f
5
+ SHA512:
6
+ metadata.gz: cf5f9ab13ff5643093f7daac5fcbcfc9cfb08f7e843b9e07cfa701978905e64a125f74d309e752cc600010e6feb222a274c274b4b0a1d2db5df3e13f7c5acafd
7
+ data.tar.gz: 00c8c8b31b4094289182568be0a93e2f22942978105118dd41318aef9cdc07f9e9e0d5039180861c71245739f666b1233693e7350fc8dfb3342e07b9f6c27416
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,391 @@
1
+ ## DataShift Journey
2
+
3
+ [![Build Status](https://travis-ci.org/autotelik/datashift_journey.svg?branch=master)](https://travis-ci.org/autotelik/datashift_journey)
4
+ [![Code Climate](https://codeclimate.com/github/autotelik/datashift_journey/badges/gpa.svg)](https://codeclimate.com/github/autotelik/datashift_journey)
5
+ [![Test Coverage](https://codeclimate.com/github/autotelik/datashift_journey/badges/coverage.svg)](https://codeclimate.com/github/autotelik/datashift_journey/coverage)
6
+
7
+ A Rails software [Wizard](https://en.wikipedia.org/wiki/Wizard_%28software%29)
8
+
9
+ Quickly create a sequence of forms (dialogs) that lead a visitor through a series
10
+ of defined steps - ideal for questionnaires, application forms, checkouts, configurations, surveys, registration processes etc.
11
+
12
+ Provides a simple DSL to quickly define a multi page journey through your site,
13
+ including complex branching, and rejoining, dependent on collected values.
14
+
15
+ State is maintained in one of the backends, with different storage models being provided
16
+ out of the box, or use your own model structure.
17
+
18
+ Full server-side processing can be delayed until the submission of the final form.
19
+
20
+ The DSL provides a simplified layer on top of a State Machine, with the main underlying gems being :
21
+
22
+ * https://github.com/state-machines/state_machines
23
+ * https://github.com/state-machines/state_machines-activerecord
24
+ * https://github.com/apotonick/reform
25
+
26
+ ## Getting started
27
+
28
+ This is a Rails engine so simply add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'datashift_journey', git: 'https://github.com/autotelik/datashift_journey'
32
+ ```
33
+ And then execute:
34
+
35
+ $ bundle install
36
+
37
+ DatashiftJourney needs a model on which to store the Wizard or Journey Plan through a state machine definition.
38
+
39
+ See below if you already have model you wish to decorate.
40
+
41
+ If you're starting from scratch, a **generator** - `rails generate datashift_journey:setup` - is provided to setup everything for you.
42
+
43
+ For example, to create a Checkout model, that will collect the data entered during a checkout journey,
44
+ such as confirm order, billing address, shipping address and payment data.
45
+
46
+ ```ruby
47
+ rails generate datashift_journey:setup Checkout
48
+ ```
49
+
50
+ This will generate a number of files, including a model file, migration to create the model table with a single column called `state`
51
+ >If using an existing model, it's **vital** that this journey class has a string column called `state`
52
+ i.e If you need to add an associated migration yourself it should contain `t.string :state`
53
+
54
+ In this example this model would be located at : `app/models/checkout.rb`
55
+
56
+ A stub for entering the journey plan is added to the model.
57
+
58
+ You should edit this model and configure your required steps in the plan.
59
+
60
+ Details of the API are supplied in comments in the file and TODO: <link>
61
+
62
+ An initializer will also be created at : `config/initializers/datashift_journey.rb`
63
+
64
+ Inside the initializer you can change which model to use as the plan and set various configuration options.
65
+
66
+ ### Defining the Journey Plan
67
+
68
+ A skeleton journey definition is added as a comment seciton to the plan model.
69
+
70
+ In here you defines the steps of the apps journey and set the initial step.
71
+
72
+ Here's a simple example for a basic checkout, on an ActiveRecord model, `Checkout`
73
+
74
+ ```ruby
75
+
76
+ class Checkout < ApplicationRecord
77
+
78
+ DatashiftJourney::Journey::MachineBuilder.create_journey_plan(initial: :ship_address) do
79
+
80
+ # Two simple sequential steps
81
+ sequence [:ship_address, :bill_address]
82
+
83
+ # At the next step, we will have a branch so first define the branch nodes - they also can
84
+ # be sequences of multiple steps, a single step, or nothing (skip straight to branch recombination step)
85
+ branch_sequence :visa_sequence, [:visa_page1]
86
+
87
+ branch_sequence :mastercard_sequence, [:page_mastercard1, :page_mastercard2]
88
+
89
+ branch_sequence :paypal_sequence, []
90
+
91
+ # Define the next state (after :bill_address, and parent state of the branch)
92
+ # and the routing criteria to each sequence
93
+ # So after bill address we reach payment - then we split to a single step, depending on the card type entered
94
+
95
+ split_on_equality( :payment,
96
+ "payment_card", # Helper method on Checkout that returns card type from Payment
97
+ visa_sequence: 'visa',
98
+ mastercard_sequence: 'mastercard',
99
+ paypal_sequence: 'paypal'
100
+ )
101
+
102
+ # All different card type branches, recombine here at review
103
+ sequence [:review, :complete ]
104
+ end
105
+
106
+ ....
107
+ ```
108
+
109
+ A state machine will be generated with all steps starting at :ship_address and finishing at :complete,
110
+ and forward and backwards navigation between them.
111
+
112
+ > *A backing Reform style Form and associated view partial, will be expected for each state.*
113
+
114
+ ### View Forms
115
+
116
+ The default controllers expect the Forms pattern to back each view.
117
+
118
+ DSJ includes the Reform gem and utilises their Form - see - https://github.com/trailblazer/reform
119
+
120
+ These tend to do the work traditionally performed in the Controller, so our controller can stay generic
121
+ and focused on navigation.
122
+
123
+ Forms give you the flexability to implement your own strategies for dealing with the presentation data required for a view,
124
+ managing params, validating entreed values, and saving the data entered into forms.
125
+
126
+ A **generator** is provided that can create skeleton Forms for you, one per state(page).
127
+
128
+ Options
129
+
130
+ > [--base-class=ClassName] # Class to use as the Base class for generated Forms
131
+
132
+ ```bash
133
+ rails generate datashift_journey:forms
134
+ ```
135
+ Generated Forms derive from `datashift_journey/app/forms/datashift_journey/base_form.rb`
136
+
137
+ And ultimately from Reform::Form
138
+
139
+ ### Views
140
+
141
+ Each step will need a view, usually a form to collect information from, but can be a static page or any content you like really.
142
+
143
+ A **generator** is provided that can create a starter set of partial views for you, one per state(page).
144
+
145
+ So, once the journey plan has been fully defined run :
146
+
147
+ ```bash
148
+ rails generate datashift_journey:views
149
+ ```
150
+
151
+ The Controller will expect a view partial, for each related Form.
152
+
153
+ The partials are rendered passing in the Form as a local variable.
154
+
155
+ The location of the partial to use for a certain state is given by helper
156
+
157
+ def journey_plan_partial_location( state )
158
+
159
+ The default is `app/views` but path can be changed using Configuration option `partial_location`
160
+
161
+ This will be required in the path format, if you are using multiple namespaces/folders
162
+
163
+ ```ruby
164
+ DatashiftJourney::Configuration.configure do |config|
165
+ config.partial_location = "checkout_engine"
166
+ end
167
+ ```
168
+ ### Data Collection
169
+ Ultimately the views and forms are there to collect data from a User, validate and store it.
170
+
171
+ Generate and use associated backing Reform forms to validate and store data, collected from your visitors.
172
+
173
+ The Reform form expects to be backed by a model, and can write data back to the model via sync and save methods,
174
+ enabling you to populate the data however you choose within your Forms - see section 'Custom Data Collector'
175
+
176
+ Alternatively a generic SQL based data collector is provided for use with the generic generated Forms to save the data.
177
+
178
+ ### Data Collector
179
+
180
+ This setup will add a migration, creating a number of tables to manage the collection of data, on a form by form basis,
181
+ stored as a series of nodes, essentially keyed on the form class and name (state).
182
+
183
+ The journey plan class is decorated with an association the the data nodes, so each instance of the journey holds all the data for that joureny,
184
+ as a collection of fields (name/value) i.e one database row per question/answer.
185
+
186
+ To use this concern as your main data collection agent, simply run the installer
187
+
188
+ ```ruby
189
+ rails generate datashift_journey:collector
190
+ ```
191
+
192
+ This will create relevant migrations and decorate your journey plan class with data collection attributes.
193
+
194
+ To access manually, derive your form from `DatashiftJourney::Collector::BaseCollectorForm`.
195
+
196
+ ### Mongo Data Collector
197
+
198
+ #### TODO
199
+
200
+ An optional MongoDB based Collector is under development, which will collect data in a single document per journey.
201
+
202
+ To use this model as your main data collection class, simply run the installer to setup the model.
203
+
204
+ ```ruby
205
+ rails generate datashift_journey:install_mongo_collector
206
+ ```
207
+
208
+ #### Journeys End
209
+
210
+ The controller should identify when the last state has been submitted and there are no further states to be rendered.
211
+
212
+ After processing the last state, the controller will redirect to the JourneyEndsController and its default new view will be rendered.
213
+
214
+ To provide your own end page, in your app simply override this view - `app/views/datashift_journey/journey_ends/new.html.erb`
215
+
216
+ You can also implement an `on_journey_end` hook, in your JourneyPlan class, which will be called by the controller
217
+ (if implemented) **before** the view is rendered.
218
+
219
+ For example, you can spin off jobs that parse and process the data
220
+
221
+ ```ruby
222
+ class Checkout < ApplicationRecord
223
+ def on_journey_end
224
+ Apoc::CreateOrderWorker.perform_async(self.id)
225
+ end
226
+ ```
227
+
228
+ #### Internals
229
+
230
+ > The Classified version of the state name, plus "Form", so a `:billing_address` state should have an associated form called `BillingAddressForm`
231
+
232
+ The DSJ Controller will search for a matching Form for each state using the Factory class/method
233
+
234
+ ## OUT OF DATE
235
+ ```ruby
236
+ DatashiftState::FormObjectFactory.form_object_for(journey_plan)
237
+ ```
238
+
239
+ In code, the expected form Class name is defined as :
240
+
241
+ ```ruby
242
+ "#{modules}::#{journey_plan.state.classify}Form"
243
+ ```
244
+
245
+ When using namespaces the *module* structure , can be set via the DSJ Configuration object,
246
+ which can be set, using a standard block format, in an initializer, as so:
247
+
248
+ ```ruby
249
+ DatashiftJourney::Configuration.configure do |config|
250
+ config.forms_module_name = 'MyCheckoutEngine'
251
+ end
252
+ ````
253
+
254
+ So given a module name configuration setting of
255
+
256
+ ```ruby
257
+ MyCheckoutEngine::States
258
+ ```
259
+
260
+ And a current state of :address - then the Controller will attempt to use Form class
261
+
262
+ ```ruby
263
+ MyCheckoutEngine::States::AddressForm
264
+ ```
265
+ ## END OUT OF DATE
266
+
267
+ #### Null Forms
268
+
269
+ When **no form** is required for a specific HTML page, you an specify that NullForm is to be used,
270
+ either globally for ALL missing forms, or for specific named forms.
271
+
272
+ The null form means no params are validated, and no save performed, for example for a mid sequence, text only
273
+ helper page, or for a text only branch terminating page.
274
+
275
+ To set globally
276
+
277
+ ```ruby
278
+ DatashiftJourney::Configuration.configure do |config|
279
+ config.use_null_form_when_no_form = true
280
+ end
281
+ ```
282
+
283
+ To set for individual states, with no data collection requirements, add to list of `null_form` states
284
+
285
+ ```ruby
286
+ DatashiftJourney::Configuration.configure do |config|
287
+ config.null_form_list = [:confirm_page_with_no_data, :brexit]
288
+ end
289
+ ```
290
+
291
+ ### DatashiftJourney::Collector
292
+
293
+ This option uses the journey plan class itself to store the data collected from each state/page in `data_nodes`
294
+
295
+ This collection is a series of `DatashiftJourney::Collector::DataNode` objects.
296
+
297
+ The generator will geneate a series of forms that inherit from `DatashiftJourney::Collector::BaseCollectorForm`
298
+ and your state form becomes very simple, for example
299
+
300
+ ```ruby
301
+ class ResourcesForm < ::BaseForm
302
+ journey_plan_form_field name: :namespace, category: :string
303
+ journey_plan_form_field name: :number_of_cpu, category: :number
304
+ journey_plan_form_field name: :memory, category: :number
305
+ end
306
+ ```
307
+
308
+ This will create field definitions, which can then be rendered automatically using the default views,
309
+ and on submit, the data entered will be saved to the DB as a DataNode, one per field name
310
+
311
+ ```JSON
312
+ DataNode: {
313
+ "id":8,
314
+ "form_field_id": 1,
315
+ "field_value": "My New Namespace"
316
+ }
317
+ ```
318
+
319
+ ### Routes
320
+
321
+ The generator will add the engines routes to your app's `config/routes.rb` file.
322
+ You can manually change the mount point to whatever suits your application.
323
+
324
+ ```ruby
325
+ Rails.application.routes.draw do
326
+ mount DatashiftJourney::Engine => "/"
327
+ end
328
+ ```
329
+
330
+ If youd like to set your apps root to be the initial state, you can manually add the following :
331
+
332
+ ```ruby
333
+ Rails.application.routes.draw do
334
+ root to: "datashift_journey/journey_plans#new"
335
+ end
336
+ ```
337
+
338
+ ### State Jumper Toolbar
339
+
340
+ There is breadcrumb style toolbar available for creating and jumping straight to any State
341
+
342
+ This must be activated by setting
343
+
344
+ ```ruby
345
+ config.add_state_jumper_toolbar = true
346
+ ```
347
+
348
+ In development, so that any data required for previous states can be created, it supports passing in a Factory
349
+ that creates that data for you.
350
+
351
+ The factory should return an instance of your DatashiftJourney.journey_plan_class
352
+
353
+ Configure your list of required 'jump to' states and factories - where no factory required simply pass nil -
354
+ by setting `state_jumper_states`, for example
355
+
356
+ ```ruby
357
+ config.state_jumper_states = {contact: my_contact_factory, ship_address: nil, :bill_address: nil}
358
+ ```
359
+
360
+ The view is added to a content_for block called :datashift_journey_state_jumper
361
+ so you can add this somewhere in your layout.
362
+
363
+ To pull in some default styling add following to your `application.css.scss`
364
+
365
+ `@import 'datashift_journey/partials/state_jumper_toolbar';`
366
+
367
+ ## License
368
+
369
+ Author :: Tom Statter
370
+
371
+ Date :: April 2016
372
+
373
+ The MIT License
374
+
375
+ Permission is hereby granted, free of charge, to any person obtaining a copy
376
+ of this software and associated documentation files (the "Software"), to deal
377
+ in the Software without restriction, including without limitation the rights
378
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
379
+ copies of the Software, and to permit persons to whom the Software is
380
+ furnished to do so, subject to the following conditions:
381
+
382
+ The above copyright notice and this permission notice shall be included in
383
+ all copies or substantial portions of the Software.
384
+
385
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
386
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
387
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
388
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
389
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
390
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
391
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ load 'rails/tasks/statistics.rake'
8
+
9
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
10
+
11
+ load 'rails/tasks/engine.rake'
12
+
13
+ Bundler::GemHelper.install_tasks
14
+
15
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
16
+
17
+ require 'rspec/core'
18
+ require 'rspec/core/rake_task'
19
+
20
+ desc "Run all specs in spec directory (excluding plugin specs)"
21
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
22
+
23
+ task :default => :spec
24
+
25
+ # Added as this was quick way to make tasks like rswag:specs:swaggerize available to the Engine
26
+ Rails.application.load_tasks
@@ -0,0 +1,27 @@
1
+ /* A simple toolbar for navigating states in DEV only - NOT FOR PRODUCTION */
2
+
3
+ <% unless(Rails.env.production?) %>
4
+
5
+ #datashift_journey_state_jumper {
6
+ }
7
+
8
+ #datashift_journey_state_jumper ul
9
+ {
10
+ margin: 0;
11
+ padding: 0;
12
+ list-style-type: none;
13
+ text-align: center;
14
+ }
15
+
16
+ #datashift_journey_state_jumper ul li { display: inline; }
17
+
18
+ #datashift_journey_state_jumper ul li a
19
+ {
20
+ font-size: 10px;
21
+ text-decoration: none;
22
+ padding: .2em 1em;
23
+ color: #fff;
24
+ background-color: #036;
25
+ }
26
+
27
+ <% end %>
@@ -0,0 +1,9 @@
1
+ module DatashiftJourney
2
+ module ErrorRenderer
3
+
4
+ def render_error(resource, status)
5
+ render json: resource, status: status, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module DatashiftJourney
2
+
3
+ module ReviewRenderer
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ def render_state_under_review(params)
8
+ # We need to track the state we are reviewing across calls.
9
+ # From first edit via the review page itself, this may come as :state
10
+ #
11
+ @rendered_state = params[:rendered_state] || params[:state]
12
+
13
+ # Once in review mode, we don't mess with the current state
14
+ # so use the over ride member @render_state_partial to drive which view to render
15
+ @render_state_partial = view_context.journey_plan_partial_location(@rendered_state)
16
+
17
+ render template: '/datashift_journey/journey_plans/edit'
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module DatashiftJourney
2
+ module TokenBasedAccess
3
+ extend ActiveSupport::Concern
4
+ # Front end journey_plan urls use the journey_plan's token as the id param
5
+ # (see #to_param in journey_plan_decorator.rb).
6
+ # A token is only really valid while the journey_plan is being created; once submitted
7
+ # it should not be possible to reference an journey_plan by token or id from the front end.
8
+ included do
9
+ def set_journey_plan
10
+ token = params[:id] || params[:journey_plan_id]
11
+
12
+ # https://github.com/robertomiranda/has_secure_token
13
+ # TODO: how to auto insert has_secure_token into the underlying journey plan model
14
+ # and add in migration thats adds the token column
15
+ # @journey_plan = DatashiftJourney.journey_plan_class.find_by_token!(token)
16
+
17
+ @journey_plan = DatashiftJourney.journey_plan_class.find(token)
18
+
19
+ logger.debug("Processing Journey: #{@journey_plan.inspect}")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ module DatashiftJourney
2
+
3
+ module ValidateState
4
+ extend ActiveSupport::Concern
5
+
6
+ # Needs further investigation into which situations require this
7
+
8
+ # http://jacopretorius.net/2014/01/force-page-to-reload-on-browser-back-in-rails.html
9
+ def back_button_cache_buster
10
+ response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
11
+ response.headers['Pragma'] = 'no-cache'
12
+ response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
13
+ end
14
+
15
+ # Currently the following Situations need special processing, carried out here.
16
+ #
17
+ # * REFRESH - users hits refresh & resubmits a form - check state associated with view
18
+ #
19
+ # It is not expected to redirect or halt processing chain - it is simply to ensure state is manged correctly,
20
+ # for situations outside standard state flow/processing
21
+ #
22
+ # rubocop:disable Style/GuardClause
23
+ def validate_state
24
+ current_index = journey_plan.current_state_index
25
+
26
+ view_state = params[:rendered_state]
27
+
28
+ view_state_idx = journey_plan.state_index(view_state) # nil for bad states
29
+
30
+ logger.debug 'STATE and Param INFO:'
31
+ logger.debug " Current State \t[#{journey_plan.state.inspect}] IDX [#{current_index}]"
32
+ logger.debug " Rendered State \t[#{view_state}] IDX [#{view_state_idx}]"
33
+ logger.debug " Redirect to \t[#{params[:redirect_to]}]"
34
+
35
+ if view_state && view_state_idx && (view_state_idx < current_index)
36
+ logger.info("Probable User refresh, resetting state #{view_state} [IDX (#{view_state_idx})]")
37
+ journey_plan.state = view_state
38
+ end
39
+
40
+ if view_state.nil? && params[:state] && back_button_param_list.all? { |p| params.key?(p) }
41
+
42
+ transitions = journey_plan.transitions_for
43
+
44
+ back_event = transitions.find { |t| t.event == :back }
45
+
46
+ if back_event && back_event.from == journey_plan.state && back_event.to == params[:state]
47
+ logger.debug("User CLICKED back Button - resetting state to [#{params[:state]}] from #{journey_plan.state}")
48
+ journey_plan.update!(state: params[:state])
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ def back_button_param_list
55
+ @back_button_param_list ||= [:state, :id, :action, :controller]
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,14 @@
1
+ module DatashiftJourney
2
+ class AbandonEnrollmentsController < DatashiftJourney::AbandonmentsController
3
+ # journey_plan object should been selected from the DB
4
+ prepend_before_filter :set_journey_plan, only: [:show]
5
+
6
+ def show
7
+ logger.info("User has abandoned #{params}")
8
+
9
+ # high voltage expects id => name of page
10
+ params[:id] = params['page']
11
+ super
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module DatashiftJourney
2
+
3
+ class AbandonmentsController < ApplicationController
4
+
5
+ include HighVoltage::StaticPage
6
+
7
+ # idea is that here was can log/update DB with any useful info on
8
+ # where/why user has abandoned, before we fwd them onto a relevant
9
+ # static helper page (N.B served up from views/pages)
10
+
11
+ def show
12
+ logger.info('User has abandoned')
13
+ super
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,49 @@
1
+ module DatashiftJourney
2
+ module Api
3
+ module V1
4
+
5
+ class StatesController < ActionController::API
6
+
7
+ def index
8
+
9
+ state_paths = []
10
+
11
+ DatashiftJourney.journey_plan_class.new.state_paths.each_with_index do |s, i|
12
+ state_paths << {
13
+ index: i, events: s.events, from_name: s.from_name, to_name: s.to_name
14
+ }
15
+ end
16
+
17
+ states = DatashiftJourney.journey_plan_class.state_machine.states.collect do |s|
18
+ {
19
+ name: s.name, value: s.value, initial: s.initial, final: s.final?
20
+ }
21
+ end
22
+
23
+ events = DatashiftJourney.journey_plan_class.state_machine.events.collect do |e|
24
+ {
25
+ #machine: s.machine,
26
+ name: e.name,
27
+ qualified_name: e.qualified_name,
28
+ human_name: e.human_name,
29
+ branches: e.branches,
30
+ known_states: e.known_states,
31
+
32
+ transitions: e.branches.map do |branch|
33
+ branch.state_requirements.map do |state_requirement|
34
+ {state_requirement: state_requirement, from: state_requirement[:from].class, to: state_requirement[:to]}
35
+ end
36
+ end
37
+ }
38
+ end
39
+
40
+ render json: { data:
41
+ {
42
+ states: states, state_paths: state_paths, events: events
43
+ }
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end