datashift_journey 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,53 @@
1
+ module DatashiftJourney
2
+
3
+ class ApplicationController < ActionController::Base
4
+
5
+ protect_from_forgery with: :exception
6
+
7
+ include TokenBasedAccess
8
+
9
+ layout ->(_) { Configuration.call.layout }
10
+
11
+ # This form does NOT seem to work - helpers cause missing method in views
12
+ # helper DatashiftJourney::Engine.helpers
13
+ # helper "datashift_journey/user"
14
+
15
+ # This form seems to work - helpers now available in views
16
+ # include DatashiftJourney::ApplicationHelper
17
+ # include DatashiftJourney::BackLinkHelper
18
+
19
+ before_action :set_i18n_locale_from_params
20
+
21
+ rescue_from ActionController::InvalidAuthenticityToken, with: :handle_invalid_authenticity_token
22
+
23
+ private
24
+
25
+ def set_i18n_locale_from_params
26
+ return unless params[:locale]
27
+
28
+ available = I18n.available_locales.map(&:to_s)
29
+
30
+ if available.include?(params[:locale])
31
+ I18n.locale = params[:locale]
32
+ logger.debug "locale set to: #{params[:locale]}"
33
+ else
34
+ flash.now[:notice] = "#{params[:locale]} translation not available"
35
+ logger.error flash.now[:notice]
36
+ end
37
+ end
38
+
39
+ # http://jacopretorius.net/2014/01/force-page-to-reload-on-browser-back-in-rails.html
40
+ def back_button_cache_buster
41
+ response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
42
+ response.headers['Pragma'] = 'no-cache'
43
+ response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
44
+ end
45
+
46
+ def handle_invalid_authenticity_token(exception)
47
+ Rails.logger.error 'DatashiftJourney::ApplicationController authenticity failed ' \
48
+ "(browser cookies may have been disabled): #{exception}"
49
+
50
+ render 'datashift_journey/errors/invalid_authenticity_token'
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ module DatashiftJourney
2
+ class ErrorsController < ApplicationController
3
+ def show
4
+ @exception = env['action_dispatch.exception']
5
+ action = request.path[1..-1].gsub(/[^0-9]/, '')
6
+ action = 500 if action.blank?
7
+
8
+ status_code =
9
+ if @exception
10
+ trace = Rails.backtrace_cleaner.clean(@exception.backtrace)
11
+ Rails.logger.fatal trace.join("#\n")
12
+
13
+ ActionDispatch::ExceptionWrapper.new(env, @exception).status_code
14
+ else
15
+ action.to_i
16
+ end
17
+
18
+ respond_to do |format|
19
+ format.html { render action: action, status: status_code }
20
+ format.json { render json: { status: status_code, error: @exception.message } }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require_dependency 'datashift_journey/application_controller'
2
+
3
+ module DatashiftJourney
4
+
5
+ class JourneyEndsController < ApplicationController
6
+
7
+ def show
8
+ journey_plan_class = params[:journey_plan_class] ? params[:journey_plan_class].constantize : DatashiftJourney.journey_plan_class
9
+
10
+ token = params[:id] || params[:journey_plan_id]
11
+
12
+ @journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
13
+
14
+ @journey_plan.on_journey_end if @journey_plan.respond_to? :on_journey_end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,166 @@
1
+ require_dependency 'datashift_journey/application_controller'
2
+
3
+ module DatashiftJourney
4
+
5
+ class JourneyPlansController < ApplicationController
6
+
7
+ include ValidateState
8
+
9
+ # Run BEFORE other filters to ensure the current journey_plan has been selected from the DB
10
+ prepend_before_action :set_journey_plan, only: [:new, :destroy, :back_a_state]
11
+
12
+ prepend_before_action :set_reform_object, only: [:create, :edit, :update]
13
+
14
+ # Validate state and state related params - covers certain edge cases such as browser refresh
15
+ before_action :validate_state, only: [:edit, :update]
16
+
17
+ def new
18
+ # Find and create the form object, backing the current states view
19
+ reform = FormObjectFactory.form_object_for(journey_plan)
20
+
21
+ render locals: { journey_plan: journey_plan, form: reform }
22
+ end
23
+
24
+ def create
25
+ # TOFIX - Validation from params is now broken
26
+ # NoMethodError (undefined method `each_with_index' for #<ActionController::Parameters:0x000055eacfa2c370>):
27
+ # result = form.validate(params)
28
+ # logger.debug("VALIDATION FAILED - Form Errors [#{form.errors.inspect}]") unless result
29
+ journey_plan = journey_plan_class.new
30
+
31
+ form_fields_to_data_nodes
32
+
33
+ move_next(journey_plan)
34
+ end
35
+
36
+ def back_a_state
37
+ respond_to do |format|
38
+ logger.debug("BACK Requested - current [#{journey_plan.state}] - previous [#{journey_plan.previous_state_name}]")
39
+
40
+ journey_plan.back!
41
+
42
+ if journey_plan.save
43
+
44
+ logger.debug("Successfully back a step - state now [#{journey_plan.state}]")
45
+
46
+ format.html do
47
+ redirect_to(datashift_journey.journey_plan_state_path(journey_plan.state, journey_plan)) && return
48
+ end
49
+
50
+ else
51
+ format.html { render :edit }
52
+ end
53
+ end
54
+ end
55
+
56
+ def edit
57
+ logger.debug "Editing journey_plan [#{journey_plan.inspect}]"
58
+ render locals: { journey_plan: @journey_plan, form: @reform }
59
+ end
60
+
61
+ def update
62
+
63
+ # result = form.validate(params)
64
+ #
65
+ # redirect_to(form.redirection_url) && return if form.redirect?
66
+ #
67
+ # if result && form.save
68
+ # logger.debug("SUCCESS - Updated #{journey_plan.inspect}")
69
+ #
70
+ # move_next(journey_plan)
71
+ # else
72
+ # logger.debug("FAILED - Form Errors [#{form.errors.inspect}]")
73
+ #
74
+ # render :edit, locals: { journey_plan: journey_plan, form: form }
75
+ # end
76
+
77
+ form_fields_to_data_nodes
78
+
79
+ logger.debug("UPDATED Plan [#{journey_plan.inspect}] - Move to Next")
80
+ puts journey_plan.inspect
81
+
82
+ move_next(journey_plan) && return
83
+ end # UPDATE
84
+
85
+ def form_fields_to_data_nodes
86
+ form_params = params.fetch(@reform.params_key, {})
87
+
88
+ data_nodes = form_params["data_nodes"] # =>{"form_field"=>{"0"=>"name", "1"=>"namespace"}, "field_value"=>{"0"=>"dfsdf", "1"=>"ghfghf"}}}
89
+
90
+ if data_nodes.present?
91
+
92
+ fields = data_nodes["form_field"]
93
+ values = data_nodes["field_value"]
94
+
95
+ fields.each do |idx, name|
96
+ ff = Collector::FormField.where(name: name, form_definition: @reform.definition).first
97
+ next unless ff
98
+
99
+ # Ensure when user goes back and changes a value we reflect the changed value
100
+ Collector::DataNode.find_or_initialize_by(plan: journey_plan, form_field: ff).tap do |node|
101
+ node.field_value = values[idx]
102
+ node.save
103
+ end
104
+
105
+ end
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def move_next(journey_plan)
112
+ journey_plan.reload
113
+
114
+ redirect_to(@reform.redirection_url) && return if @reform.redirect?
115
+
116
+ if journey_plan.next_state_name.blank?
117
+ logger.error("JOURNEY ENDED - no next transition - rendering 'journey_end'")
118
+ redirect_to(datashift_journey.journey_end_path(journey_plan)) && return
119
+ end
120
+
121
+ # if there is no next event, state_machine dynamic helper can_next? not available
122
+ unless journey_plan.respond_to?('can_skip_fwd?') && journey_plan.can_skip_fwd?
123
+ logger.error("JOURNEY Cannot proceed - no next transition - rendering 'journey_end'")
124
+ redirect_to(datashift_journey.journey_end_path(journey_plan)) && return
125
+ end
126
+
127
+ journey_plan.skip_fwd!
128
+
129
+ logger.info("JOURNEY moved to next state - Current Plan :")
130
+ logger.info(journey_plan)
131
+
132
+ redirect_to(datashift_journey.journey_plan_state_path(journey_plan.state, journey_plan)) && return
133
+ end
134
+
135
+ def set_journey_plan_class
136
+ @journey_plan_class = params[:journey_plan_class] ? params[:journey_plan_class].constantize : DatashiftJourney.journey_plan_class
137
+ end
138
+
139
+ def set_journey_plan
140
+ set_journey_plan_class
141
+
142
+ token = params[:id] || params[:journey_plan_id]
143
+
144
+ # TOFIX: Rails 6 probably has this inbuilt now and makes this much simpler - see ActiveSupport::MessageEncryptor, KeyGenerator
145
+ #
146
+ # https://github.com/robertomiranda/has_secure_token
147
+ # TODO: how to auto insert has_secure_token into the underlying journey plan model
148
+ # and add in migration thats adds the token column
149
+ # @journey_plan = DatashiftJourney.journey_plan_class.find_by_token!(token)
150
+ @journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
151
+ end
152
+
153
+ def set_reform_object
154
+ set_journey_plan_class
155
+
156
+ token = params[:id] || params[:journey_plan_id]
157
+
158
+ journey_plan = token ? journey_plan_class.find(token) : journey_plan_class.new
159
+ @reform = FormObjectFactory.form_object_for(journey_plan)
160
+
161
+ @journey_plan = @reform.journey_plan
162
+ end
163
+
164
+ attr_reader :journey_plan_class, :journey_plan
165
+ end
166
+ end
@@ -0,0 +1,49 @@
1
+ module DatashiftJourney
2
+ class FormsController < ActionController::API
3
+
4
+ include DatashiftJourney::ErrorRenderer
5
+
6
+ before_action :set_user, only: [:show, :update, :destroy]
7
+
8
+ # PageState contains details for rendering and storing a Page related to a single State
9
+ #
10
+ def index
11
+ @collector_forms = Collector::FormDefinition.all
12
+
13
+ render json: @collector_forms, status: :ok
14
+ end
15
+
16
+ def create
17
+ @collector_form = Collector::FormDefinition.new(page_state_params)
18
+
19
+ if @collector_form.save
20
+ render json: @collector_form, status: :created
21
+ else
22
+ render_error(@collector_form, :unprocessable_entity) and return
23
+ #render json: { errors: @collector_form.errors }, status: :unprocessable_entity
24
+ end
25
+ end
26
+
27
+ def show
28
+ render json: @collector_form
29
+ end
30
+
31
+ private
32
+
33
+ # Only allow a trusted parameter "white list" through.
34
+ def page_state_params
35
+ params.fetch(:page_state, {}).permit(:form_name)
36
+ end
37
+
38
+ def set_user
39
+ begin
40
+ @collector_form = Collector::FormDefinition.find params[:id]
41
+ rescue ActiveRecord::RecordNotFound
42
+ collector_form = Collector::FormDefinition.new
43
+ collector_form.errors.add(:id, "Wrong ID provided")
44
+ render_error(collector_form, 404) and return
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module DatashiftJourney
2
+
3
+ class ReviewsController < ApplicationController
4
+
5
+ include DatashiftJourney::ReviewRenderer
6
+
7
+ # We want this to run BEFORE other filters to ensure the current
8
+ # journey_plan object has been selected from the DB
9
+
10
+ prepend_before_filter :set_journey_plan, only: [:edit]
11
+
12
+ def edit
13
+ logger.info("Sent from REVIEW [#{params.inspect}]")
14
+
15
+ respond_to do |format|
16
+ format.html do
17
+ setup_view_data_for_state(params['state'])
18
+
19
+ render_state_under_review(params)
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # Never trust parameters, only allow the white list through.
27
+ def review_params
28
+ params.permit(:id, :state)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # This is a development tool - for creating and jumping straight to any State
2
+ #
3
+ # So that any data required for previous states can be created, it supports passing in a optional Factory
4
+ # that creates that data for you.
5
+ #
6
+ # The factory should return an instance of your DatashiftJourney.journey_plan_class
7
+ #
8
+ # For a full list of available factories you can call :
9
+ #
10
+ # FactoryGirl.definition_file_paths.inspect
11
+ #
12
+ # FactoryGirl.factories.collect(&:name).inspect
13
+ #
14
+ module DatashiftJourney
15
+ class StateJumperController < ApplicationController
16
+
17
+ unless Rails.env.production?
18
+
19
+ require 'factory_girl'
20
+ require 'faker'
21
+
22
+ def build_and_display
23
+ state = params['state']
24
+ factory = params['factory']
25
+
26
+ plan = if factory
27
+ # Get weird problems with factories with has_many associations, when they've already been used,
28
+ # so while obviously not very efficient, this seems to prevent that issue
29
+ FactoryGirl.reload
30
+
31
+ Rails.logger.debug("Building StateJumper #{DatashiftJourney.journey_plan_class} from [#{factory}]")
32
+
33
+ FactoryGirl.create(factory)
34
+ else
35
+ DatashiftJourney.journey_plan_class.create(state: state)
36
+ end
37
+
38
+ if plan.state != state
39
+ plan.skip_fwd(state.to_sym) until !plan.can_skip_fwd? || plan.state == state
40
+ end
41
+
42
+ plan.update_attribute(:state, state) if plan.state != state
43
+
44
+ Rails.logger.debug("Jumping to STATE [#{plan.state}]")
45
+
46
+ redirect_to(datashift_journey.journey_plan_state_path(state, plan)) && return
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,68 @@
1
+ require 'reform/form'
2
+
3
+ module DatashiftJourney
4
+ class FormObjectFactory
5
+
6
+ class << self
7
+
8
+ # Create a form object from the current states Form class
9
+ #
10
+ # Each form can provide a factory to drive how its constructed, or can rely on the base classes factory method
11
+ #
12
+ def form_object_for(journey_plan)
13
+ # Get ReForm form for current state
14
+ klass = form_class_for(journey_plan)
15
+ raise(FormObjectError, "Failed to load form class #{form_name(journey_plan.state)} for state #{journey_plan.state}") unless klass
16
+
17
+ # Create new instance of form for current journey instance
18
+ klass.new(journey_plan)
19
+ end
20
+
21
+ def form_name(state)
22
+ @form_name_mod ||= Configuration.call.forms_module_name
23
+
24
+ "#{@form_name_mod}::#{state.to_s.camelize}Form"
25
+ end
26
+
27
+ def state_name(form)
28
+ return form.chomp('Form').underscore if form.is_a?(String)
29
+ form.class.name.chomp('Form').underscore
30
+ end
31
+
32
+ private
33
+
34
+ def null_form_for_state(journey_plan)
35
+ return DatashiftJourney::NullForm if Configuration.call.use_null_form_when_no_form
36
+
37
+ return DatashiftJourney:: NullForm if null_form_requirested?(journey_plan.state)
38
+
39
+ nil
40
+ end
41
+
42
+ def null_form_requirested?(state)
43
+ null_form_list.include?(state.to_s)
44
+ end
45
+
46
+ def null_form_list
47
+ @null_form_list ||= Configuration.call.null_form_list.map!(&:to_s)
48
+ end
49
+
50
+ # Find the current state and its associated Form
51
+ def form_class_for(journey_plan)
52
+ form_name(journey_plan.state).constantize
53
+ rescue => x
54
+ null_form = null_form_for_state(journey_plan)
55
+
56
+ unless null_form
57
+ Rails.logger.debug(x.backtrace.first)
58
+ Rails.logger.debug(x.inspect)
59
+ Rails.logger.debug("No Form class found for state #{journey_plan.state} - #{x.message}")
60
+ return nil
61
+ end
62
+
63
+ null_form
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ require_relative '../concerns/form_mixin'
2
+
3
+ module DatashiftJourney
4
+ module Collector
5
+
6
+ # This class represents the View backing Form
7
+ #
8
+ # Reform API :
9
+ #
10
+ # initialize always requires a model that the form represents.
11
+ # validate(params) updates the form's fields with the input data (only the form, not the model) and then runs all validations. The return value is the boolean result of the validations.
12
+ # errors returns validation messages in a classic ActiveModel style.
13
+ # sync writes form data back to the model. This will only use setter methods on the model(s).
14
+ # save (optional) will call #save on the model and nested models. Note that this implies a #sync call.
15
+ # prepopulate! (optional) will run pre-population hooks to "fill out" your form before rendering.
16
+ #
17
+ class BaseCollectorForm < Reform::Form
18
+
19
+ include DatashiftJourney::FormMixin
20
+
21
+ feature Reform::Form::Dry # override the default.
22
+
23
+ attr_accessor :definition
24
+
25
+ # Called from CONTROLLER
26
+ #
27
+ # Creates a form object backed by the current Plan object
28
+ #
29
+ # Data is collected generically from fields defined by FormDefinition and stored
30
+ # in data nodes associated with current JourneyPlan instance (through polymorphic plan association)
31
+ #
32
+ def initialize(journey_plan)
33
+ super(journey_plan)
34
+
35
+ @journey_plan = journey_plan
36
+
37
+ @definition = DatashiftJourney::Collector::FormDefinition.where(klass: self.class.name).first
38
+
39
+ # For brand new forms, add one data node per form field - data nodes hold the COLLECTED VALUES
40
+ # If this page already been visited we should have a completed data node already
41
+ form_definition.form_fields.map(&:id).each do |id|
42
+ next if journey_plan.data_nodes.where('form_field_id = ?', id).exists?
43
+ journey_plan.data_nodes << DatashiftJourney::Collector::DataNode.new(plan: journey_plan, form_field_id: id)
44
+ end
45
+
46
+ end
47
+
48
+ # Over ride in your form if your view forms have non standard key field.
49
+ #
50
+ # The default naming format for form elements in the view is : "#{params_key}[data_nodes][field_value][#{i}]"
51
+ #
52
+ # For example:
53
+ # <%= select_tag "#{params_key}[data_nodes][field_value][#{i}]".... %>
54
+ def params_key
55
+ DatashiftJourney::FormObjectFactory.state_name(self.class.name)
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ require_dependency 'reform'
2
+
3
+ module DatashiftJourney
4
+
5
+ # Collection of tools to support the Forms
6
+ module FormMixin
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ # These forms are used to back Views so need to be able to prepare and present data for views
11
+ include ActionView::Helpers::FormOptionsHelper
12
+
13
+ attr_reader :journey_plan
14
+ attr_accessor :redirection_url
15
+
16
+ def validate(params)
17
+ Rails.logger.debug "VALIDATING #{model.inspect} - Params - #{form_params(params)}"
18
+ super form_params(params)
19
+ end
20
+
21
+ def redirect?
22
+ Rails.logger.debug "Checking for REDIRECTION - [#{redirection_url}]"
23
+ !redirection_url.nil?
24
+ end
25
+
26
+ class_methods do
27
+
28
+ def form_definition
29
+ # In this situation self is the class of the including form eg PaymentForm, AddressFrom
30
+ @form_definition ||= DatashiftJourney::Collector::FormDefinition.find_or_create_by!(klass: self.name)
31
+ end
32
+
33
+ # Form helper to add fields inside a class definition
34
+ #
35
+ # N.B Currently this will create a permanent entry in the DB,
36
+ # so removing this code will not remove the Field - must be deleted from DB
37
+ #
38
+ # Usage
39
+ #
40
+ # journey_plan_form_field name: :model_uri, category: :string
41
+ # journey_plan_form_field name: :run_time, category: :select_option
42
+ # journey_plan_form_field name: :memory, category: :number
43
+ #
44
+ def journey_plan_form_field(name:, category:)
45
+ DatashiftJourney::Collector::FormField.find_or_create_by!(form_definition: form_definition, name: name, category: category)
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def form_params(params)
52
+ params.fetch(params_key, {})
53
+ end
54
+
55
+ def logger
56
+ Rails.logger
57
+ end
58
+
59
+ def form_definition
60
+ @form_definition ||= self.class.form_definition
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,20 @@
1
+ require_dependency 'reform'
2
+
3
+ # A specialized Form class for use with states that do not require Form functionality,
4
+ # just render the Partial
5
+
6
+ module DatashiftJourney
7
+ class NullForm < BaseForm
8
+ def self.factory(journey_plan)
9
+ new(journey_plan)
10
+ end
11
+
12
+ def save
13
+ true
14
+ end
15
+
16
+ def validate(_params)
17
+ true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ module DatashiftJourney
2
+ module ApplicationHelper
3
+
4
+ # This is the main hook to insert a States partial view into the main Form
5
+ def render_if_exists(state, *args)
6
+ lookup_context.prefixes.prepend DatashiftJourney::Configuration.call.partial_location
7
+
8
+ render(state, *args) if lookup_context.exists?(state, lookup_context.prefixes, true)
9
+ end
10
+
11
+ # Returns true if '_state' partial exists in configured location (Configuration.partial_location)
12
+
13
+ def journey_plan_partial?(state)
14
+ return true if lookup_context.exists?(state, [DatashiftJourney::Configuration.call.partial_location], true)
15
+
16
+ Rails.logger.warn("DSJ - No partial found for [#{state}] in path(s) [#{lookup_context.prefixes.inspect}]")
17
+
18
+ false
19
+ end
20
+
21
+ # helper to return the location of a partial for a particular state
22
+ def journey_plan_partial_location(state)
23
+ Rails.logger.debug("DatashiftJourney RENDER #{DatashiftJourney::Configuration.call.partial_location}/#{state}}")
24
+ File.join(DatashiftJourney::Configuration.call.partial_location.to_s, state)
25
+ end
26
+
27
+ def error_link_id(attribute)
28
+ # with nested attributes can get full path e.g applicant_contact.full_name
29
+ # we only want the last field
30
+ field = attribute.to_s.split(/\./).last
31
+ "form_group_#{field}"
32
+ end
33
+
34
+ def all_errors(record)
35
+ record.class.reflect_on_all_associations.each do |a|
36
+ assoc = @journey_plan.send(a.name)
37
+ next unless assoc && assoc.respond_to?(:errors)
38
+ assoc.errors.full_messages.each do |_message|
39
+ "<li><a href='<%= message %>'></a></li>"
40
+ end
41
+ end
42
+ end
43
+
44
+ def friendly_date(date)
45
+ formatted_date = date && l(date.to_date, format: :long)
46
+ formatted_date || ''
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ module DatashiftJourney
2
+ module BackLinkHelper
3
+
4
+ # Helper to create a standard Back link that will jump back to previous state
5
+ def back_a_state_link(journey_plan = nil, css: nil)
6
+ DatashiftJourney::BackLink.new(request, engine_routes: datashift_journey, journey_plan: journey_plan, css: css).tag
7
+ end
8
+ end
9
+ end