guide 0.0.1 → 0.5.0

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 (83) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +22 -0
  3. data/Rakefile +18 -0
  4. data/app/assets/javascripts/guide/application.js +21723 -0
  5. data/app/assets/javascripts/guide/scenario.js +12642 -0
  6. data/app/assets/stylesheets/guide/application.css +141 -0
  7. data/app/controllers/guide/base_controller.rb +83 -0
  8. data/app/controllers/guide/nodes_controller.rb +40 -0
  9. data/app/controllers/guide/scenarios_controller.rb +44 -0
  10. data/app/helpers/guide/application_helper.rb +4 -0
  11. data/app/helpers/guide/document_helper.rb +20 -0
  12. data/app/models/guide/arborist.rb +44 -0
  13. data/app/models/guide/bouncer.rb +34 -0
  14. data/app/models/guide/cartographer.rb +49 -0
  15. data/app/models/guide/content.rb +28 -0
  16. data/app/models/guide/default_authentication_system.rb +13 -0
  17. data/app/models/guide/default_authorisation_system.rb +18 -0
  18. data/app/models/guide/diplomat.rb +60 -0
  19. data/app/models/guide/document.rb +20 -0
  20. data/app/models/guide/endpoint_stocktaker.rb +94 -0
  21. data/app/models/guide/errors.rb +2 -0
  22. data/app/models/guide/errors/base.rb +6 -0
  23. data/app/models/guide/errors/interface_violation.rb +2 -0
  24. data/app/models/guide/errors/invalid_node.rb +2 -0
  25. data/app/models/guide/errors/invalid_scenario.rb +2 -0
  26. data/app/models/guide/errors/invalid_visibility_option.rb +2 -0
  27. data/app/models/guide/errors/permission_denied.rb +2 -0
  28. data/app/models/guide/fixture.rb +15 -0
  29. data/app/models/guide/fixtures.rb +3 -0
  30. data/app/models/guide/form_object.rb +3 -0
  31. data/app/models/guide/monkey.rb +47 -0
  32. data/app/models/guide/nobilizer.rb +9 -0
  33. data/app/models/guide/node.rb +67 -0
  34. data/app/models/guide/photographer.rb +20 -0
  35. data/app/models/guide/scout.rb +59 -0
  36. data/app/models/guide/simulator.rb +26 -0
  37. data/app/models/guide/structure.rb +75 -0
  38. data/app/models/guide/view_model.rb +37 -0
  39. data/app/view_models/guide/layout_view.rb +81 -0
  40. data/app/view_models/guide/navigation_view.rb +25 -0
  41. data/app/view_models/guide/node_view.rb +59 -0
  42. data/app/view_models/guide/scenario_layout_view.rb +49 -0
  43. data/app/view_models/guide/scenario_view.rb +39 -0
  44. data/app/views/guide/_content.html.erb +17 -0
  45. data/app/views/guide/common/_category.html.erb +9 -0
  46. data/app/views/guide/common/_footer.html.erb +21 -0
  47. data/app/views/guide/common/_locale_switcher.html.erb +6 -0
  48. data/app/views/guide/common/_navigation.html.erb +12 -0
  49. data/app/views/guide/common/_navigation_node.html.erb +39 -0
  50. data/app/views/guide/common/_page_title.html.erb +8 -0
  51. data/app/views/guide/common/_search.html.erb +23 -0
  52. data/app/views/guide/common/_unsupported_browser_message.html.erb +6 -0
  53. data/app/views/guide/common/_visibility_banner.html.erb +25 -0
  54. data/app/views/guide/nodes/_document.html.erb +3 -0
  55. data/app/views/guide/nodes/_scenario.html.erb +14 -0
  56. data/app/views/guide/nodes/_scenario_list.html.erb +10 -0
  57. data/app/views/guide/nodes/_structure.html.erb +26 -0
  58. data/app/views/guide/nodes/_template_location.html.erb +7 -0
  59. data/app/views/guide/nodes/show.html.erb +7 -0
  60. data/app/views/guide/scenarios/_scenario.html.erb +25 -0
  61. data/app/views/guide/scenarios/scenario/_locale_switcher.html.erb +5 -0
  62. data/app/views/guide/scenarios/scenario/_toolbar.html.erb +39 -0
  63. data/app/views/guide/scenarios/scenario/_visibility.html.erb +10 -0
  64. data/app/views/guide/scenarios/show.html.erb +1 -0
  65. data/app/views/layouts/guide/application.html.erb +76 -0
  66. data/app/views/layouts/guide/scenario.html.erb +38 -0
  67. data/app/views/layouts/guide/scenario/default.html.erb +1 -0
  68. data/app/views/layouts/guide/scenario/default.text.erb +1 -0
  69. data/config/initializers/assets.rb +1 -0
  70. data/config/locales/models/guide/monkey.en.yml +6 -0
  71. data/config/locales/views/guide/nodes/_structure.en.yml +7 -0
  72. data/config/routes.rb +6 -0
  73. data/lib/guide.rb +12 -0
  74. data/lib/guide/authorisation_spec_helper.rb +83 -0
  75. data/lib/guide/configuration.rb +29 -0
  76. data/lib/guide/consistency_spec_helper.rb +33 -0
  77. data/lib/guide/engine.rb +9 -0
  78. data/lib/guide/version.rb +3 -0
  79. data/lib/tasks/guide_tasks.rake +4 -0
  80. metadata +207 -16
  81. data/.gitignore +0 -26
  82. data/Gemfile +0 -4
  83. data/guide.gemspec +0 -12
@@ -0,0 +1,59 @@
1
+ class Guide::Scout
2
+ def initialize(starting_node)
3
+ @starting_node = starting_node
4
+
5
+ ensure_starting_node_exists
6
+ end
7
+
8
+ def visibility_along_path(node_path)
9
+ current_node = @starting_node
10
+ path_visibility = current_node.options[:visibility]
11
+
12
+ node_ids_along_path(node_path).each do |node_id|
13
+ current_node = current_node.child_nodes[node_id]
14
+ ensure_node_exists(current_node, node_id)
15
+ path_visibility = foggiest_visibility(path_visibility,
16
+ current_node.options[:visibility])
17
+ end
18
+
19
+ path_visibility
20
+ end
21
+
22
+ private
23
+
24
+ VISIBILITY_PRIORITY = {
25
+ nil => 0,
26
+ :unpublished => 1,
27
+ :restricted => 2,
28
+ }
29
+ private_constant :VISIBILITY_PRIORITY
30
+
31
+ def foggiest_visibility(first, second)
32
+ case
33
+ when VISIBILITY_PRIORITY[first] > VISIBILITY_PRIORITY[second]
34
+ first
35
+ when VISIBILITY_PRIORITY[first] < VISIBILITY_PRIORITY[second]
36
+ second
37
+ else
38
+ first
39
+ end
40
+ end
41
+
42
+ def node_ids_along_path(node_path)
43
+ @node_ids ||= node_path.split("/").map {|node_id| node_id.to_sym }
44
+ end
45
+
46
+ def ensure_node_exists(node, node_id)
47
+ unless node.present?
48
+ raise Guide::Errors::InvalidNode,
49
+ "I can't give you what you're looking for because a node in your path (#{node_id}) doesn't exist."
50
+ end
51
+ end
52
+
53
+ def ensure_starting_node_exists
54
+ unless @starting_node.present?
55
+ raise Guide::Errors::InvalidNode,
56
+ "I can't give you what you're looking for because the node that you told me to start from doesn't exist. This means that something is fundamentally wrong with your setup."
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ class Guide::Simulator
2
+ def initialize(structure, bouncer)
3
+ @structure = structure
4
+ @bouncer = bouncer
5
+ end
6
+
7
+ def fetch_scenario(scenario_id)
8
+ scenario(scenario_id).tap do |scenario|
9
+ ensure_user_can_access(scenario)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def scenario(scenario_id)
16
+ @structure.scenarios[scenario_id].tap do |scenario|
17
+ raise Guide::Errors::InvalidScenario unless scenario.present?
18
+ end
19
+ end
20
+
21
+ def ensure_user_can_access(scenario)
22
+ unless @bouncer.user_can_access?(scenario)
23
+ raise Guide::Errors::PermissionDenied
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,75 @@
1
+ class Guide::Structure < Guide::Node
2
+ def template
3
+ partial
4
+ end
5
+
6
+ def partial
7
+ end
8
+
9
+ def cell
10
+ nil
11
+ end
12
+
13
+ def layout_css_classes
14
+ {}
15
+ end
16
+
17
+ def formats
18
+ [:html]
19
+ end
20
+
21
+ def stylesheets
22
+ Guide.configuration.default_stylesheets_for_structures
23
+ end
24
+
25
+ def javascripts
26
+ Guide.configuration.default_javascripts_for_structures
27
+ end
28
+
29
+ def self.scenario(id, **options, &block)
30
+ private define_method(id, block)
31
+
32
+ scenario_definitions[id] = OpenStruct.new(
33
+ :name => id.to_s.titleize,
34
+ :options => OpenStruct.new(**options),
35
+ )
36
+ end
37
+
38
+ class_attribute :scenario_definitions
39
+
40
+ def self.inherited(sub_class)
41
+ sub_class.scenario_definitions = {}
42
+ super(sub_class)
43
+ end
44
+
45
+ def scenarios
46
+ @scenarios ||=
47
+ scenario_definitions.each.with_object({}) do |(id, scenario_definition), scenario_hash|
48
+ scenario_hash[id] = OpenStruct.new(
49
+ :name => scenario_definition.name,
50
+ :view_model => send(id),
51
+ :options => scenario_definition.options,
52
+ )
53
+ end
54
+ end
55
+
56
+ def layout_templates
57
+ {
58
+ :html => default_layout_template
59
+ }
60
+ end
61
+
62
+ def layout_view_model
63
+ Guide::ViewModel.new
64
+ end
65
+
66
+ def can_be_rendered?
67
+ true
68
+ end
69
+
70
+ private
71
+
72
+ def default_layout_template
73
+ Guide.configuration.default_layout_for_scenarios
74
+ end
75
+ end
@@ -0,0 +1,37 @@
1
+ class Guide::ViewModel < OpenStruct
2
+ def initialize(defaults = {}, overrides = {})
3
+ inappropriate_overrides = overrides.keys - defaults.keys
4
+ if inappropriate_overrides.any?
5
+ raise Guide::Errors::InterfaceViolation.new(
6
+ "You added the #{'method'.pluralize(inappropriate_overrides.size)} "\
7
+ "[#{inappropriate_overrides.join(", ")}] to the #{self.class.name} "\
8
+ "in your scenario that #{'is'.pluralize(inappropriate_overrides.size)} "\
9
+ "not included in its official declaration "\
10
+ "(maybe in the `view_model` method on your Structure)"
11
+ )
12
+ end
13
+
14
+ @guide_view_model_interface_methods = defaults.keys
15
+
16
+ super(defaults.merge(overrides))
17
+ end
18
+
19
+ def method_missing(method, *args, &block)
20
+ if respond_to?(method)
21
+ super
22
+ else
23
+ raise Guide::Errors::InterfaceViolation.new(
24
+ "You called a method '#{method}' from your template, "\
25
+ "but it does not exist on the #{self.class.name} in your Structure."
26
+ )
27
+ end
28
+ end
29
+
30
+ def guide_view_model_interface_methods
31
+ @guide_view_model_interface_methods
32
+ end
33
+
34
+ def to_ary
35
+ nil # because Cells calls ViewModel#flatten for some reason
36
+ end
37
+ end
@@ -0,0 +1,81 @@
1
+ class Guide::LayoutView
2
+ attr_reader :active_node_visibility, :active_node_heritage, :active_node_title
3
+
4
+ def initialize(bouncer:,
5
+ diplomat:,
6
+ content_node:,
7
+ active_node:,
8
+ active_node_heritage:,
9
+ active_node_visibility:,
10
+ active_node_title:,
11
+ authentication_system:,
12
+ injected_html:)
13
+ @bouncer = bouncer
14
+ @diplomat = diplomat
15
+ @content_node = content_node
16
+ @active_node = active_node
17
+ @active_node_heritage = active_node_heritage
18
+ @active_node_visibility = active_node_visibility
19
+ @active_node_title = active_node_title
20
+ @authentication_system = authentication_system
21
+ @injected_html = injected_html
22
+ end
23
+
24
+ def active_node_name
25
+ @active_node.name
26
+ end
27
+
28
+ def on_homepage?
29
+ @active_node == @content_node
30
+ end
31
+
32
+ def injected_html
33
+ @injected_html.html_safe
34
+ end
35
+
36
+ def paths_to_visible_renderable_nodes
37
+ cartographer.draw_paths_to_visible_renderable_nodes(starting_node: @content_node)
38
+ end
39
+
40
+ def user_is_privileged?
41
+ @bouncer.user_is_privileged?
42
+ end
43
+
44
+ def user_signed_in?
45
+ @authentication_system.user_signed_in?
46
+ end
47
+
48
+ def url_for_sign_in
49
+ @authentication_system.url_for_sign_in
50
+ end
51
+
52
+ def url_for_sign_out
53
+ @authentication_system.url_for_sign_out
54
+ end
55
+
56
+ def supported_locales
57
+ @diplomat.supported_locales
58
+ end
59
+
60
+ def show_locale_switcher?
61
+ @bouncer.user_is_privileged? && @diplomat.multiple_supported_locales?
62
+ end
63
+
64
+ def current_locale
65
+ @diplomat.current_locale
66
+ end
67
+
68
+ def locale_param
69
+ 'locale'
70
+ end
71
+
72
+ def show_image_logo?
73
+ Guide.configuration.asset_path_for_logo.present?
74
+ end
75
+
76
+ private
77
+
78
+ def cartographer
79
+ @cartographer ||= Guide::Cartographer.new(@bouncer)
80
+ end
81
+ end
@@ -0,0 +1,25 @@
1
+ class Guide::NavigationView
2
+ delegate :id, :name, :leaf_node?, :to => :@node
3
+
4
+ def initialize(bouncer:, node:, active_node:)
5
+ @bouncer = bouncer
6
+ @node = node
7
+ @active_node = active_node
8
+ end
9
+
10
+ def child_node_views
11
+ @node.child_nodes.map do |child_node_id, child_node|
12
+ self.class.new(bouncer: @bouncer,
13
+ node: child_node,
14
+ active_node: @active_node)
15
+ end
16
+ end
17
+
18
+ def visible_to_user?
19
+ @bouncer.user_can_access?(@node)
20
+ end
21
+
22
+ def active?
23
+ @node == @active_node
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ class Guide::NodeView
2
+ delegate :id,
3
+ :options,
4
+ :name,
5
+ :formats,
6
+ :cell,
7
+ :template,
8
+ :layout_css_classes,
9
+ :node_type,
10
+ :can_be_rendered?,
11
+ :view_model, :to => :@node
12
+
13
+ attr_reader :node_path
14
+
15
+ def initialize(node:, bouncer:, diplomat:, node_path:)
16
+ @node = node
17
+ @bouncer = bouncer
18
+ @diplomat = diplomat
19
+ @node_path = node_path
20
+ end
21
+
22
+ def visible_scenarios
23
+ @node.scenarios.select do |scenario_id, scenario|
24
+ @bouncer.user_can_access?(scenario)
25
+ end
26
+ end
27
+
28
+ def multiple_formats?
29
+ @node.formats.size > 1
30
+ end
31
+
32
+ def uses_cells?
33
+ cell.present?
34
+ end
35
+
36
+ def template_location
37
+ template || cell
38
+ end
39
+
40
+ def user_is_privileged?
41
+ @bouncer.user_is_privileged?
42
+ end
43
+
44
+ def supported_locales
45
+ @diplomat.supported_locales
46
+ end
47
+
48
+ def show_locale_switcher?
49
+ @bouncer.user_is_privileged? && @diplomat.multiple_supported_locales?
50
+ end
51
+
52
+ def current_locale
53
+ @diplomat.current_locale
54
+ end
55
+
56
+ def locale_param
57
+ 'temp_locale'
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ class Guide::ScenarioLayoutView
2
+ def initialize(node:, node_title:, scenario:, format:, injected_html:)
3
+ @node = node
4
+ @node_title = node_title
5
+ @scenario = scenario
6
+ @format = format
7
+ @injected_html = injected_html
8
+ end
9
+
10
+ def node_title
11
+ @node_title
12
+ end
13
+
14
+ def scenario_name
15
+ @scenario.name
16
+ end
17
+
18
+ def node_layout_template
19
+ @node.layout_templates[format] || Guide.configuration.default_layout_for_scenarios
20
+ end
21
+
22
+ def node_layout_view
23
+ @node.layout_view_model
24
+ end
25
+
26
+ def format
27
+ @format
28
+ end
29
+
30
+ def inject_stylesheets?
31
+ @format == 'html'
32
+ end
33
+
34
+ def node_stylesheets
35
+ @node.stylesheets
36
+ end
37
+
38
+ def inject_javascripts?
39
+ @format == 'html'
40
+ end
41
+
42
+ def node_javascripts
43
+ @node.javascripts
44
+ end
45
+
46
+ def injected_html
47
+ @injected_html.html_safe
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ class Guide::ScenarioView
2
+ def initialize(node:, scenario:, format:)
3
+ @node = node
4
+ @scenario = scenario
5
+ @format = format
6
+ end
7
+
8
+ def uses_cells?
9
+ @node.cell.present?
10
+ end
11
+
12
+ def cell
13
+ @node.cell
14
+ end
15
+
16
+ def template
17
+ @node.template || @node.partial
18
+ end
19
+
20
+ def format
21
+ @format
22
+ end
23
+
24
+ def view
25
+ @scenario.view
26
+ end
27
+
28
+ def view_model
29
+ @scenario.view_model
30
+ end
31
+
32
+ def layout_css_classes
33
+ @node.layout_css_classes || ""
34
+ end
35
+
36
+ def wrapper_classes
37
+ @scenario.options.custom_wrapper_css || ""
38
+ end
39
+ end