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
@@ -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