guide 0.0.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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