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,39 @@
1
+ ---
2
+ en:
3
+ datashift_journey:
4
+ shared:
5
+ errors:
6
+ you_have: You have
7
+ global:
8
+ back: Back
9
+ continue: Continue
10
+ journey_plan:
11
+ continue: Continue
12
+ 404_notfound:
13
+ heading: Page not found
14
+ message1: Sorry, the page you’re looking for cannot be found.
15
+ message2: Please check the web address you entered was correct.
16
+ 422_unprocessable_entity:
17
+ heading: The change you wanted was rejected
18
+ message1: Maybe you tried to change something you didn't have access to?
19
+ 500_internal_server_error:
20
+ heading: Something went wrong
21
+ message1: Sorry, there is a technical problem.
22
+ message2: Please try again in a few minutes.
23
+ siteHeader_html: Override global.siteHeader_html in en.yml
24
+ 401_unauthorized:
25
+ heading: Unauthorised
26
+ message1: You are not permitted to see this page.
27
+ message2: This page is restricted to certain users only.
28
+ 403_forbidden:
29
+ # As per https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
30
+ # We do not wish to make this information available to the client, therefore using messaging for 404-not-found instead.
31
+ heading: Page not found
32
+ message1: Sorry, the page you’re looking for cannot be found.
33
+ message2: Please check the web address you entered was correct.
34
+ 503_bad_gateway:
35
+ heading: Service unavailable
36
+ message1: Please try again later. If the problem persists, call the Environment Agency helpline 03708 506 506.
37
+ invalid_authenticity_token:
38
+ heading: The cookies in your browser have been disabled
39
+ message_html: "To carry on with your registration please enable session %{link} in your browser."
data/config/routes.rb ADDED
@@ -0,0 +1,44 @@
1
+ DatashiftJourney::Engine.routes.draw do
2
+
3
+ resources :journey_plans, only: [:create, :new, :edit, :update]
4
+
5
+ # TODO - Devise generates routes based on module selection .. like we are trying to do with optional Collector
6
+ # https://github.com/plataformatec/devise/blob/88724e10adaf9ffd1d8dbfbaadda2b9d40de756a/lib/devise/rails/routes.rb
7
+
8
+ # TO INVESTIGATE - On an error processing a state user is redirected but this goes to
9
+ # => http://localhost:3000/journey_plans/(:id) via a get- which is index or show
10
+ # This currently fixes the issue so a refresh leaves user on right page
11
+ get '/journey_plans(.:format)', :to => 'journey_plans#create'
12
+ get '/journey_plans/:id', :to => 'journey_plans#edit'
13
+
14
+ # Note on using get rather than patch :
15
+ # We use these to provide nicer links - to jump between states & fwd/backwards,
16
+ # but if the Visitor has JS disabled, link_to falls back to get even if patch, put etc specified
17
+ get 'journey_plans/state/back/*id', :to => '/datashift_journey/journey_plans#back_a_state', :as => :back_a_state
18
+
19
+ # These forms enables us to copy and paste url and see the state in the browser url
20
+ get '/journey_plans/:state/:id', :to => 'journey_plans#edit', :as => :journey_plan_state
21
+
22
+ # factory is optional - without it will create bare bones journey_class object with state set
23
+ get '/state_jumper/:state/(:factory)', :to => 'state_jumper#build_and_display', :as => :build_and_display
24
+
25
+ resources :journey_ends, only: [:show]
26
+
27
+ =begin
28
+
29
+ patch '/journey_plans/update/:state', :to => 'journey_plans#update', :as => :update_journey_plan
30
+
31
+ get '/journey_plans/:id', :to => 'journey_plans#edit'
32
+
33
+ get 'reviews/:state/*id', :to => '/datashift_journey/reviews#edit', :as => :review_state
34
+
35
+ # We have Abandonment derived from high voltage to manage static content
36
+ # related to User's abandoning
37
+ get "/abandonments/*id" => 'abandonments#show', as: :abandonment, format: false
38
+
39
+ # includes the high voltage static page to display
40
+ get "/abandon_journey_plans/:page/:id", to: 'abandon_journey_plans#show', as: :abandon_journey_plan
41
+ =end
42
+
43
+ match '(errors)/:status', to: 'errors#show', via: :all, constraints: { status: /\d{3}/ }
44
+ end
@@ -0,0 +1,12 @@
1
+ module DatashiftJourney
2
+ module Collector
3
+ class FieldSnippet < ActiveRecord::Base
4
+
5
+ self.table_name = 'dsj_fields_snippets'
6
+
7
+ belongs_to :form_field
8
+ belongs_to :snippet
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module DatashiftJourney
2
+ module Collector
3
+ class PageStateSnippet < ActiveRecord::Base
4
+
5
+ self.table_name = 'dsj_page_states_snippets'
6
+
7
+ belongs_to :page_state
8
+ belongs_to :snippet
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module DatashiftJourney
2
+ module Collector
3
+
4
+ class Snippet < ActiveRecord::Base
5
+
6
+ self.table_name = 'dsj_snippets'
7
+
8
+ def self.to_sentance(snippets)
9
+ snippets.collect { |s| s.I18n_key.present? ? I18n.t(s.I18n_key) : s.raw_text }.join(' ')
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,103 @@
1
+ module DatashiftJourney
2
+
3
+ # Helper struct for displaying available account types in view
4
+ Struct.new('StateJumperEntry', :state, :display, :factory)
5
+
6
+ class Configuration
7
+
8
+ # The start page link for journey page Back links
9
+ #
10
+ # @param [String<#call>]
11
+ # @return [String<#call>]
12
+ #
13
+ attr_writer :backto_start_url
14
+
15
+ # The module(s) under which to find Forms
16
+ # Form factory will look for a Form class related to a state called
17
+ #
18
+ # "#{mod}::#{journey_plan.state}Form"
19
+ #
20
+ # @param [String<#call>] module name under which Forms reside
21
+ # @return [String<#call>]
22
+ #
23
+ attr_accessor :forms_module_name
24
+
25
+ # When no Form is required for a specific HTML page, you an specify that a NullForm is to be used
26
+ # by adding that state to this list
27
+ #
28
+ # @param [Array<#call>] List of states that require only a NullForm
29
+ # @return [Array<#call>]
30
+ #
31
+ attr_accessor :null_form_list
32
+
33
+ # Always use a NullForm when no designated Form for a page
34
+ # @param [Boolean<#call>]
35
+ # @return [Boolean<#call>]
36
+ #
37
+ attr_accessor :use_null_form_when_no_form
38
+
39
+ # The location of the View partials, for rendering
40
+ #
41
+ # default is empty, for views stored directly in app/views
42
+ #
43
+ attr_accessor :partial_location
44
+
45
+ attr_accessor :layout
46
+
47
+ # In development, you can add a state jumper toolbar, for jumping straight to any state
48
+ #
49
+ attr_accessor :add_state_jumper_toolbar
50
+
51
+ # Add required details of states to jump to (see StateJumperEntry) to this ARRAY
52
+ #
53
+ # So that any data required for previous states can be created, it supports passing in a Factory
54
+ # that creates that data for you by value. Use nil if no data required
55
+ #
56
+ # The factory should return an instance of your DatashiftJourney.journey_plan_class
57
+ #
58
+ # @param [ StateJumperEntry.new(:state_name, "State Name", :state_factory), .... ]
59
+ #
60
+ attr_accessor :state_jumper_states
61
+
62
+ def initialize
63
+ @forms_module_name = ''
64
+
65
+ @partial_location = ''
66
+
67
+ @use_null_form_when_no_form = false
68
+ @null_form_list = []
69
+
70
+ @layout = 'application'
71
+ @add_state_jumper_toolbar = false
72
+ @state_jumper_states = []
73
+ end
74
+
75
+ def backto_start_url
76
+ @backto_start_url ||= Rails.application.routes.url_helpers.root_path
77
+ end
78
+
79
+ # @return [DatashiftJourney::Configuration] current configuration
80
+ def self.call
81
+ @configuration ||= DatashiftJourney::Configuration.new
82
+ end
83
+
84
+ def self.reset
85
+ @configuration = DatashiftJourney::Configuration.new
86
+ end
87
+
88
+ # @param config [DatashiftJourney::Configuration]
89
+ class << self
90
+ attr_writer :configuration
91
+ end
92
+
93
+ # Modify current DatashiftJourney configuration
94
+ # ```
95
+ # DatashiftJourney::Configuration.configure do |config|
96
+ # config.html_only = false
97
+ # end
98
+ # ```
99
+ def self.configure
100
+ yield call
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,38 @@
1
+ begin
2
+ require_relative 'exceptions'
3
+ require_relative 'configuration'
4
+ require_relative 'journey/machine_builder'
5
+
6
+ require_relative 'reference_generator'
7
+ require_relative 'helpers/back_link'
8
+
9
+ rescue => x
10
+ # TODO: - remove this block once gem stable
11
+ puts x.inspect
12
+ end
13
+
14
+ module DatashiftJourney
15
+
16
+ class Engine < ::Rails::Engine
17
+
18
+ isolate_namespace DatashiftJourney
19
+
20
+ # Add a load path for this specific Engine
21
+ config.autoload_paths += Dir["#{config.root}/lib"]
22
+
23
+ # Make Shared examples and Support files available to Apps and other Engines
24
+ # TODO: - make this optional - i.e installable so Apps/Engines can easily pull this in themselves if they wish
25
+ # if Rails.env.test? && defined?(RSpec)
26
+ # initializer 'datashift_journey.shared_examples' do
27
+ # RSpec.configure do
28
+ # Dir[File.join(File.expand_path('../../../spec/shared_examples', __FILE__), '**/*.rb')].each { |f| require f }
29
+ # Dir[File.join(File.expand_path('../../../spec/support', __FILE__), '**/*.rb')].each { |f| require f }
30
+ # end
31
+ # end
32
+ #
33
+ # config.autoload_paths << File.expand_path('../../../spec/support', __FILE__)
34
+ # end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,26 @@
1
+ module DatashiftJourney
2
+
3
+ class CoreException < StandardError
4
+
5
+ def initialize(msg)
6
+ super
7
+ Rails.logger.error(msg)
8
+ end
9
+
10
+ def self.generate(name)
11
+ new_class = Class.new(CoreException) do
12
+ def initialize(msg)
13
+ super(msg)
14
+ end
15
+ end
16
+
17
+ DatashiftJourney.const_set(name, new_class)
18
+ end
19
+
20
+ end
21
+
22
+ class FormObjectError < CoreException; end
23
+ class PlannerApiError < CoreException; end
24
+ class PlannerBlockError < CoreException; end
25
+
26
+ end
@@ -0,0 +1,58 @@
1
+ #
2
+ # Helper class for constructing back links for navigating backward through the journey
3
+ #
4
+ module DatashiftJourney
5
+
6
+ class BackLink
7
+
8
+ include ActionView::Helpers::UrlHelper
9
+
10
+ attr_reader :css, :current_request, :journey_plan, :engine_routes
11
+
12
+ def initialize(request, engine_routes:, journey_plan: nil, css: nil)
13
+ @current_request = request
14
+ @engine_routes = engine_routes
15
+ @journey_plan = journey_plan
16
+ @css = css
17
+ end
18
+
19
+ def tag(text = nil, html_opts = {})
20
+ if journey_plan && (journey_plan.can_back?)
21
+ title, url = link_arguments(text)
22
+ link_to title, url, html_opts.merge(class: css || 'journey-plan-back-link')
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def link_arguments(text = nil)
29
+ [text || link_text, link_url]
30
+ end
31
+
32
+ def link_text
33
+ I18n.t(journey_plan ? 'global.back' : 'global.back_to_start_link')
34
+ end
35
+
36
+ def link_url
37
+ # TODO: Implement automatic reviewable
38
+ # return journey_plan_reviewing_path if journey_plan.try! :under_review?
39
+
40
+ return start_url unless journey_plan
41
+
42
+ journey_plan_back_url
43
+ end
44
+
45
+ def start_url
46
+ DatashiftJourney::Configuration.call.backto_start_url
47
+ end
48
+
49
+ def journey_plan_back_url
50
+ engine_routes.back_a_state_url(journey_plan)
51
+ end
52
+
53
+ def journey_plan_reviewing_path
54
+ engine_routes.journey_plan_state_path('reviewing', journey_plan)
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,59 @@
1
+ module DatashiftJourney
2
+ module Journey
3
+
4
+ # N.B This gem is based on the AR integration which uses the Machine name for the Column storing the state in
5
+ # The default name and column is :state
6
+ #
7
+ class MachineBuilder
8
+
9
+ # The available API is defined in : ../state_machines/planner.rb
10
+ #
11
+ # DatashiftJourney::Journey::MachineBuilder.create_journey_plan(initial: :deployment) do
12
+ # sequence [:deployment,
13
+ # :default_predicator,
14
+ # :version,
15
+ # :launch]
16
+ # end
17
+ #
18
+ # Args
19
+ # :machine_name => :something_different # Requires Class has a string column also called something_different
20
+ # :initial => :first_state
21
+ #
22
+ def self.create_journey_plan(args = {}, &block)
23
+ DatashiftJourney.journey_plan_class.send :include, DatashiftJourney::StateMachines::Extensions
24
+ DatashiftJourney.journey_plan_class.send :extend, DatashiftJourney::StateMachines::Extensions
25
+
26
+ create_journey_plan_for(DatashiftJourney.journey_plan_class, args, &block)
27
+ end
28
+
29
+ # Args
30
+ # :machine_name => :something_different # Requires Class has a column called something_different
31
+ # :initial => :first_state
32
+ #
33
+ def self.create_journey_plan_for(klass, args = {}, &block)
34
+ machine_name = args.key?(:machine_name) ? args.delete(:machine_name) : :state
35
+
36
+ puts "Building journey plan State Machine [#{machine_name}] for [#{klass}]"
37
+
38
+ machine = if block_given?
39
+ klass.class_eval do
40
+ state_machine machine_name, args do
41
+
42
+ init_plan
43
+
44
+ instance_eval(&block) # sets the context to be StateMachine class'
45
+
46
+ # Journey has been pre parsed, we have the states, - now build navigation events and transitions
47
+ instance_eval('build_journey_plan')
48
+ end
49
+ end
50
+ else
51
+ ::StateMachines::Machine.new(klass, machine_name, args)
52
+ end
53
+ machine
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,219 @@
1
+ module DatashiftJourney
2
+
3
+ # module PrepareDataForReview
4
+ #
5
+ # extend ActiveSupport::Concern
6
+ #
7
+ # def self.included(base)
8
+ # base.send :include, InstanceMethods
9
+ # end
10
+ #
11
+ # module InstanceMethods
12
+ #
13
+ # # You can build your review page within a YAML config file (locale file)
14
+ # # It supports a simple DSL in below format.
15
+ # #
16
+ # # SYNTAX :
17
+ # # Indentation, usually 2 spaces, or a 2 space TAB, is very important
18
+ # # <> are used to illustrate the elements that accept free text
19
+ # #
20
+ # # ROW :
21
+ # #
22
+ # # Each ReviewDataRow has upto 3 elements
23
+ # #
24
+ # # :title: The string for Column 1, the row header
25
+ # #
26
+ # # :method: The actual database data for Column2, method to call on the model, or the association object
27
+ # #
28
+ # # :link_state: The state the change me links, jumps back to.
29
+ # # Optional, if unspecified, uses the section block (state)
30
+ # #
31
+ # # FULL DSL :
32
+ # #
33
+ # # <key>:
34
+ # # sections:
35
+ # # <section_block - usually a state>: # The Link target defaults to this, if no explicit state set
36
+ # # section_heading: "Farming data" # The section title - that spans the 3 columns
37
+ # # direct: # Non association data i.e directly on the parent model
38
+ # # - :title: "Activity is on a farm" # Column title. The - indicates a list, each mini block is a row
39
+ # # :method: on_a_farm # Method called on association to get real (DB) data to display
40
+ # # - :title: "Registrant is a farmer" # Next mini block - Each section can have multiple rows
41
+ # # :method: is_a_farmer
42
+ # # <next section_block - choosing_site_address>:
43
+ # # section_heading: Waste Activity Location
44
+ # # associations:
45
+ # # <association> # As well as direct, supports Rails associations, on supplied model
46
+ # # - :title: "1st Column"
47
+ # # :method: full_name # Ruby method called on association, returns real (DB) data to display
48
+ # # :link_state: state # The link target - i.e state to JUMP to, in order to EDIT the data
49
+ # # site_address: # Again, section can contain multiple rows, from multiple associations
50
+ # # - :title: "Address"
51
+ # # :method: # No method supplied, data will be WHOLE associated object
52
+ # #
53
+ # # EXAMPLE:
54
+ # #
55
+ # # journey_plan_review:
56
+ # # sections:
57
+ # # choosing_farming_data:
58
+ # # section_heading: "Farming data"
59
+ # # direct:
60
+ # # - :title: "Activity is on a farm"
61
+ # # :method: on_a_farm
62
+ # # - :title: "Registrant is a farmer"
63
+ # # :method: is_a_farmer
64
+ # # choosing_site_address:
65
+ # # section_heading: Waste Activity Location
66
+ # # associations:
67
+ # # site_address:
68
+ # # - :title: "Address"
69
+ # # :method: # No state cos Partial can render WHOLE Address object
70
+ # # - :title: "Grid reference"
71
+ # # :method: grid_reference
72
+ # # choosing_exemption:
73
+ # # section_heading: Waste Exemption Codes
74
+ # # associations:
75
+ # # exemption:
76
+ # # - :title: "Waste Exemptions"
77
+ # # :method: code
78
+ # #
79
+ # # NOTES
80
+ # #
81
+ # # Method can be left blank, in this case the data is the association object, so this must be display-able
82
+ # #
83
+ # # For example, this will cause @journey_plan.site_address to be sent to the review partial, which we know has
84
+ # # special use case for Address, which renders a display address partial
85
+ # #
86
+ # # site_address:
87
+ # # - :title: "Address"
88
+ # # :method:
89
+ # #
90
+ # attr_reader :review_data
91
+ #
92
+ # # rubocop:disable Metrics/MethodLength
93
+ #
94
+ # def prepare_from_locale(model, locale_key = '.journey_plan_review')
95
+ # sections = I18n.t("#{locale_key}.sections", default: {})
96
+ #
97
+ # Rails.logger.debug("Review Sections: #{sections.inspect}")
98
+ #
99
+ # unless sections.is_a?(Hash)
100
+ # Rails.logger.error("Bad syntax in your review YAML - Expect a 'sections' Hash element")
101
+ # raise "Bad syntax in your review YAML - Expect a 'sections' Hash element"
102
+ # end
103
+ #
104
+ # @model_object = model
105
+ #
106
+ # # Return a collection of Sections (ReviewData), each section has multiple rows
107
+ #
108
+ # sections.collect do |section, data|
109
+ # next unless data[:section_heading]
110
+ #
111
+ # @current_section = section
112
+ #
113
+ # @review_data = ReviewDataSection.new(data[:section_heading])
114
+ #
115
+ # # Possible Enhancement required :
116
+ # # As it stands this will NOT preserve the Order as defined exactly in the YAML as it's a HASH
117
+ # # with keys - for direct & association sections - which are unordered.
118
+ # # So currently had to make arbitrary decision to process direct first, then associations
119
+ #
120
+ # # Direct data on parent Model, where key is - direct:
121
+ # key = "#{locale_key}.sections.#{section}.direct"
122
+ #
123
+ # I18n.t(key).each { |column| row_to_review_data(model_object, column) } if I18n.exists?(key)
124
+ #
125
+ # # Associated data - children of parent
126
+ # key = "#{locale_key}.sections.#{current_section}.associations"
127
+ #
128
+ # if I18n.exists?(key)
129
+ #
130
+ # association_list = I18n.t("#{locale_key}.sections.#{current_section}.associations", default: [])
131
+ #
132
+ # association_list.each do |association_data|
133
+ # unless association_data.size == 2
134
+ # Rails.logger.error('Bad syntax in your review YAML - expect each association to have name and fields')
135
+ # next
136
+ # end
137
+ #
138
+ # # Each association should have a row defined as a list i.e Array
139
+ # # - :title: Business trading name
140
+ # # :name: full_name
141
+ # #
142
+ # unless association_data[1].respond_to?(:each)
143
+ # Rails.logger.error('Bad syntax in review YAML - each row needs a title, method and optional link')
144
+ # next
145
+ # end
146
+ #
147
+ # # The first element is the association name or chain,
148
+ # # i.e method(s) to call on the parent model to reach child with the actual data
149
+ # association_method = association_data[0].to_s
150
+ #
151
+ # review_object = begin
152
+ # find_association(association_method)
153
+ # rescue => e
154
+ # Rails.logger.error(e.message)
155
+ # Rails.logger.error("Bad syntax in review YAML - Could not load associated object #{association_method}")
156
+ # next
157
+ # end
158
+ #
159
+ # unless review_object
160
+ # Rails.logger.error("Nil association for #{association_method} on #{model} - no review data available")
161
+ # next
162
+ # end
163
+ #
164
+ # # The second element is a list of rows, made up of title, method to call on association and the state link
165
+ # association_data[1].each { |column| row_to_review_data(review_object, column) }
166
+ # end
167
+ # end
168
+ #
169
+ # review_data
170
+ # end
171
+ # end
172
+ #
173
+ # private
174
+ #
175
+ # attr_accessor :current_section, :current_review_object, :review_data
176
+ #
177
+ # attr_accessor :model_object
178
+ #
179
+ # def row_to_review_data(review_object, row)
180
+ # # The section name, can be used as the state, for linking whole section, rather than at field level
181
+ # link_state = row[:link_state] || current_section
182
+ # link_title = row[:link_title]
183
+ #
184
+ # @current_review_object = review_object
185
+ #
186
+ # # The review partial can support whole objects, or low level data from method call defined in the DSL
187
+ # if row[:method].blank?
188
+ # review_data.add(row[:title], review_object, link_state.to_s, link_title)
189
+ # else
190
+ # # rubocop:disable Style/IfInsideElse
191
+ # if review_object.respond_to?(:each)
192
+ # review_object.each do |o|
193
+ # @current_review_object = o
194
+ # review_data.add(row[:title], send_chain(row[:method]), link_state.to_s, link_title)
195
+ # end
196
+ # else
197
+ # review_data.add(row[:title], send_chain(row[:method]), link_state.to_s, link_title)
198
+ # end
199
+ #
200
+ # end
201
+ # end
202
+ #
203
+ # def find_association(method_chain)
204
+ # method_chain.to_s.split('.').inject(model_object) { |a, e| a.send(e) }
205
+ # end
206
+ #
207
+ # def send_chain(method_chain)
208
+ # arr = method_chain.to_s.split('.')
209
+ # begin
210
+ # arr.inject(current_review_object) { |a, e| a.send(e) }
211
+ # rescue => e
212
+ # Rails.logger.error("Failed to process method chain #{method_chain} : #{e.message}")
213
+ # return I18n.t('.journey_plan_review.missing_data')
214
+ # end
215
+ # end
216
+ #
217
+ # end
218
+ # end
219
+ end
@@ -0,0 +1,51 @@
1
+ module DatashiftJourney
2
+
3
+ class ReferenceGenerator < Module
4
+ BASE = 10
5
+ DEFAULT_LENGTH = 9
6
+ NUMBERS = (0..9).to_a.freeze
7
+ LETTERS = ('A'..'Z').to_a.freeze
8
+
9
+ attr_accessor :prefix, :length
10
+
11
+ def initialize(options)
12
+ @random = Random.new
13
+ @prefix = options.fetch(:prefix)
14
+ @length = options.fetch(:length, DEFAULT_LENGTH)
15
+ @candidates = NUMBERS + (options[:letters] ? LETTERS : [])
16
+ end
17
+
18
+ def included(host)
19
+ generator_method = method(:generate_permalink)
20
+ generator_instance = self
21
+
22
+ host.class_eval do
23
+ validates(:reference, presence: true, uniqueness: { allow_blank: true })
24
+
25
+ before_validation do |instance|
26
+ instance.reference ||= generator_method.call(host)
27
+ end
28
+
29
+ define_singleton_method(:reference_generator) { generator_instance }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def generate_permalink(host)
36
+ length = @length
37
+
38
+ loop do
39
+ candidate = new_candidate(length)
40
+ return candidate unless host.exists?(reference: candidate)
41
+
42
+ # If over half of all possible options are taken add another digit.
43
+ length += 1 if host.count > Rational(BASE**length, 2)
44
+ end
45
+ end
46
+
47
+ def new_candidate(length)
48
+ @prefix + Array.new(length) { @candidates.sample(random: @random) }.join
49
+ end
50
+ end
51
+ end