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