mariner 0.0.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 (62) hide show
  1. data/.autotest +10 -0
  2. data/.gitignore +18 -0
  3. data/.pairs +6 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +12 -0
  7. data/LICENSE +22 -0
  8. data/README.md +59 -0
  9. data/Rakefile +13 -0
  10. data/lib/mariner.rb +49 -0
  11. data/lib/mariner/errors.rb +26 -0
  12. data/lib/mariner/helper.rb +96 -0
  13. data/lib/mariner/railtie.rb +21 -0
  14. data/lib/mariner/renderer/base.rb +42 -0
  15. data/lib/mariner/store.rb +76 -0
  16. data/lib/mariner/unordered_list_renderer.rb +202 -0
  17. data/lib/mariner/url.rb +54 -0
  18. data/lib/mariner/version.rb +3 -0
  19. data/mariner.gemspec +20 -0
  20. data/spec/integrations/unordered_list_renderer_spec.rb +90 -0
  21. data/spec/mariner_spec.rb +29 -0
  22. data/spec/navigation/helper_spec.rb +131 -0
  23. data/spec/navigation/railtie_spec.rb +17 -0
  24. data/spec/navigation/renderer/base_spec.rb +25 -0
  25. data/spec/navigation/store_spec.rb +80 -0
  26. data/spec/navigation/unordered_list_render_spec.rb +93 -0
  27. data/spec/navigation/url_spec.rb +45 -0
  28. data/spec/rails_app/.gitignore +15 -0
  29. data/spec/rails_app/Rakefile +7 -0
  30. data/spec/rails_app/app/assets/javascripts/application.js +15 -0
  31. data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
  32. data/spec/rails_app/app/controllers/application_controller.rb +6 -0
  33. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  34. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  35. data/spec/rails_app/app/models/.gitkeep +0 -0
  36. data/spec/rails_app/app/views/application/condensed.html.erb +1 -0
  37. data/spec/rails_app/app/views/application/normal.html.erb +1 -0
  38. data/spec/rails_app/app/views/layouts/application.html.erb +12 -0
  39. data/spec/rails_app/config.ru +4 -0
  40. data/spec/rails_app/config/application.rb +64 -0
  41. data/spec/rails_app/config/boot.rb +3 -0
  42. data/spec/rails_app/config/database.yml +3 -0
  43. data/spec/rails_app/config/environment.rb +5 -0
  44. data/spec/rails_app/config/environments/test.rb +37 -0
  45. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  46. data/spec/rails_app/config/initializers/inflections.rb +15 -0
  47. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  48. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  49. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  50. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  51. data/spec/rails_app/config/locales/en.yml +5 -0
  52. data/spec/rails_app/config/routes.rb +9 -0
  53. data/spec/rails_app/db/.gitkeep +0 -0
  54. data/spec/rails_app/lib/assets/.gitkeep +0 -0
  55. data/spec/rails_app/lib/tasks/.gitkeep +0 -0
  56. data/spec/rails_app/log/.gitkeep +0 -0
  57. data/spec/rails_app/script/rails +6 -0
  58. data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
  59. data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
  60. data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
  61. data/spec/spec_helper.rb +8 -0
  62. metadata +175 -0
@@ -0,0 +1,202 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module Mariner
4
+
5
+ # Public: The default renderer used. Renders the nav config to
6
+ # an unordered list.
7
+ #
8
+ # Examples:
9
+ #
10
+ # Mariner.configure do
11
+ #
12
+ # a_group do
13
+ # root_path 'A link'
14
+ # end
15
+ #
16
+ # end
17
+ #
18
+ # Mariner.configuration.render #=> returns:
19
+ #
20
+ # <ul class=''>
21
+ # <li>
22
+ # <a class="root_path" href="/">A Link</a>
23
+ # </li>
24
+ # </ul>
25
+ #
26
+ #
27
+ # If you want to implement your own rendering strategy, read on
28
+ # for a good starting point.
29
+ #
30
+ #
31
+ # class FakeRenderingStrategy
32
+ #
33
+ # # Called from Store and Url instances. Must return something
34
+ # # that responds to #render
35
+ # #
36
+ # def factory(type, subject)
37
+ # # type - Can be `:group` or `:item`
38
+ # # subject - The Store or Url instance to render
39
+ # case type
40
+ # when :group then FakeRenderingStrategy::GroupRenderer.new(subject, self)
41
+ # when :item then FakeRenderingStrategy::ItemRenderer.new(subject, self)
42
+ # end
43
+ # end
44
+ #
45
+ # class GroupRenderer < RenderingStrategy::Base
46
+ #
47
+ # # A group renderer must iterate its #configurations and call
48
+ # # #render on each, then return the joined result.
49
+ # #
50
+ # def render
51
+ # subject.configurations.map do |config|
52
+ # _, entity = config
53
+ # entity.render(rendering_strategy) # entity can be a Store or a Url
54
+ # end.join
55
+ # end
56
+ #
57
+ # end
58
+ #
59
+ # class ItemRenderer < RenderingStrategy::Base
60
+ #
61
+ # # An item renderer just needs to return some string representation
62
+ # # of the Url it's rendering.
63
+ # #
64
+ # def render
65
+ # # render something based on #subject (e.g. markup for a link)
66
+ # end
67
+ #
68
+ # end
69
+ #
70
+ # end
71
+
72
+ class UnorderedListRenderer
73
+
74
+ # Public: If true, will render a title list element based
75
+ # on the name of the group.
76
+ #
77
+ attr_accessor :render_titles
78
+ alias :render_titles? :render_titles
79
+
80
+ # Public: The classname that will be added to the rendered
81
+ # UL elements.
82
+ #
83
+ attr_accessor :group_classname
84
+
85
+ # Public: The classname that will be added to the rendered
86
+ # title LI elements
87
+ #
88
+ attr_accessor :title_classname
89
+
90
+ # Public: The classname that will be added to the rendered
91
+ # links (A elements)
92
+ #
93
+ attr_accessor :item_classname
94
+
95
+ # Public: Initialize a new renderer with options.
96
+ #
97
+ # options - The hash of options used to determine how to render:
98
+ # :render_titles - Turn title rendering for Stores on or off (optional, default: false)
99
+ #
100
+ def initialize(options={})
101
+ defaults.merge(options).each { |k,v| send("#{k}=", v) }
102
+ end
103
+
104
+ # Public: Called by Store and Url instances to get a renderer.
105
+ #
106
+ # Examples:
107
+ #
108
+ # s = UnorderedListRenderer.new
109
+ # s.factory(:group, self) #=> instance of UnorderedListRenderer::GroupRenderer
110
+ # s.factory(:item, self) #=> instance of UnorderedListRenderer::ItemRenderer
111
+ #
112
+ def factory(type, subject)
113
+ case type
114
+ when :group then GroupRenderer.new(subject, self)
115
+ when :item then ItemRenderer.new(subject, self)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def defaults
122
+ { :render_titles => false }
123
+ end
124
+
125
+ public
126
+
127
+ class ItemRenderer < Renderer::Base
128
+ include ActionView::Helpers::TagHelper
129
+
130
+ # Public: Renders an A element according to the Url properties.
131
+ #
132
+ def render
133
+ content_tag :a, subject.title, render_options
134
+ end
135
+
136
+ # Public: The options that the A element will be rendered with. These
137
+ # are forwarded on to ActionView::Helpers::TagHelper#content_tag
138
+ #
139
+ def render_options
140
+ opts = { :href => subject.href, :class => subject.name.to_s }
141
+ opts.merge(:class => rendering_strategy.item_classname, &merge_proc).merge(subject.options, &merge_proc)
142
+ end
143
+
144
+ private
145
+
146
+ # Private: Used internally to determine how to merge options. For
147
+ # `:class`, add the values together with a space. For everything else,
148
+ # replace the value.
149
+ #
150
+ def merge_proc
151
+ proc do |key, oldval, newval|
152
+ case key.to_s
153
+ when "class"
154
+ (oldval.to_s.split(" ") + newval.to_s.split(" ")).sort.join(" ")
155
+ else newval
156
+ end
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ class GroupRenderer < Renderer::Base
163
+
164
+ # Public: Render a Store as an unordererd list HTML element.
165
+ #
166
+ def render
167
+ open_surround = close_surround = item_open_surround = item_close_surround = ""
168
+
169
+ open_surround = "<ul class='#{rendering_strategy.group_classname}'>"
170
+ close_surround = "</ul>"
171
+
172
+ item_open_surround = "<li>"
173
+ item_close_surround = "</li>"
174
+
175
+ rendered_configurations = subject.configurations.map do |config|
176
+ name, entity = config
177
+ unless subject.virtual?
178
+ "#{item_open_surround}#{entity.render(rendering_strategy)}#{item_close_surround}"
179
+ else
180
+ entity.render(rendering_strategy)
181
+ end
182
+ end.join
183
+
184
+ unless subject.virtual?
185
+ result = ""
186
+ result << open_surround
187
+
188
+ if rendering_strategy.render_titles?
189
+ result << "<li class='#{rendering_strategy.title_classname}'>#{subject.name.to_s.titleize}#{item_close_surround}"
190
+ end
191
+
192
+ result << "#{rendered_configurations}#{close_surround}"
193
+ else
194
+ rendered_configurations
195
+ end
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ end
@@ -0,0 +1,54 @@
1
+ module Mariner
2
+
3
+ # Public: Represents a Url configuration.
4
+ #
5
+ # Examples:
6
+ #
7
+ # Mariner.configure do
8
+ # a_group do
9
+ # root_path "A Link" # <= This ends up being a Url instance.
10
+ # end
11
+ # end
12
+ #
13
+ class Url
14
+
15
+ attr_accessor :name, :title, :options
16
+
17
+ # Public: Create a new Url
18
+ #
19
+ # name - The method name that corresponds to a Rails route helper
20
+ # title - The text that should be rendered to represent a url.
21
+ # options - Any link options that should be present. Used by the
22
+ # renderer (default: {})
23
+ #
24
+ def initialize(name, title, options={})
25
+ @name, @title, @options = name, title, options
26
+ end
27
+
28
+ # Public: Renders itself through a rendering strategy.
29
+ #
30
+ # rendering_strategy - The rendering strategy to use. Must respond to
31
+ # #render (default: UnorderedListRenderer.new)
32
+ #
33
+ def render(rendering_strategy=Mariner.rendering_strategies[:default])
34
+ rendering_strategy.factory(:item, self).render
35
+ end
36
+
37
+ # Public: Calls the Rails route helper method to get its href.
38
+ #
39
+ def href
40
+ begin
41
+ # The link's name should be the method name
42
+ # for a rails route helper. We're including the route
43
+ # helpers in an initializer, so this will only fail if
44
+ # the route is undefined.
45
+ #
46
+ send(name)
47
+ rescue NoMethodError => e
48
+ raise ::Mariner::Errors::InvalidUrlHelperMethod.new(name)
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,3 @@
1
+ module Mariner
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/mariner/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jesse Trimble", "Adam Albrecht"]
6
+ gem.email = ["jlowelltrim@gmail.com", "adam.albrecht@gmail.com"]
7
+ gem.description = %q{A DSL for creating navigation structures based on the Rails routing table using Abyss.}
8
+ gem.summary = %q{A DSL for creating navigation structures based on the Rails routing table using Abyss.}
9
+ gem.homepage = ""
10
+
11
+ gem.add_dependency 'abyss'
12
+ gem.add_dependency 'rails', '~> 3.2.0'
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "mariner"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = Mariner::VERSION
20
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ # This test suite is kind of a hack just to access
4
+ # the capybara matchers.
5
+
6
+ # TODO: Figure out how to do this better.
7
+
8
+ module Mariner
9
+
10
+ describe UnorderedListRenderer do
11
+
12
+ before do
13
+ Mariner.configure do
14
+ quick_links do
15
+ root_path "Home"
16
+ end
17
+ end
18
+ end
19
+
20
+ after { Mariner.configuration = nil }
21
+
22
+ # Work within the context of an anonymous controller
23
+ #
24
+ describe Class.new(ActionController::Base), :type => :controller do
25
+
26
+ subject { get :index; response.body }
27
+
28
+ describe "rendered element classnames" do
29
+
30
+ controller do
31
+ def index
32
+ render_options = {
33
+ :render_titles => true,
34
+ :group_classname => 'nav-list',
35
+ :title_classname => 'nav-title',
36
+ :item_classname => 'nav-item'
37
+ }
38
+ strategy = UnorderedListRenderer.new(render_options)
39
+ render :text => Mariner.configuration.render(strategy)
40
+ end
41
+ end
42
+
43
+ it "uses the classnames provided" do
44
+ subject.should have_css "ul.nav-list li:first-child.nav-title"
45
+ subject.should have_css "ul.nav-list li:last-child a.nav-item"
46
+ end
47
+
48
+ end
49
+
50
+ describe "the rendering of titles" do
51
+
52
+ context "when render_titles is false on the rendering strategy" do
53
+
54
+ controller do
55
+ def index
56
+ strategy = UnorderedListRenderer.new(:render_titles => false)
57
+ render :text => Mariner.configuration.render(strategy)
58
+ end
59
+ end
60
+
61
+ it "suppresses titles" do
62
+ subject.should_not have_css 'ul li:first-child', :text => 'Quick Links'
63
+ subject.should have_css 'ul li:last-child a[href="/"]', :text => 'Home'
64
+ end
65
+
66
+ end
67
+
68
+ context "when render_titles is true" do
69
+
70
+ controller do
71
+ def index
72
+ strategy = UnorderedListRenderer.new(:render_titles => true)
73
+ render :text => Mariner.configuration.render(strategy)
74
+ end
75
+ end
76
+
77
+ it "suppresses titles" do
78
+ subject.should have_css 'ul li:first-child', :text => 'Quick Links'
79
+ subject.should have_css 'ul li:last-child a[href="/"]', :text => 'Home'
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ module Mariner
4
+
5
+ describe ".configure" do
6
+
7
+ before { Mariner.configuration = nil }
8
+ after { Mariner.configuration = nil }
9
+
10
+ it 'is shorthand for the navigation Config API' do
11
+ expected_block = Proc.new { }
12
+ fake_config = mock().as_null_object
13
+ Store.stub(:new).and_return(fake_config)
14
+ fake_config.should_receive(:instance_eval).with(&expected_block)
15
+
16
+ Mariner.configure &expected_block
17
+ end
18
+
19
+ it 'sets the root-level group as virtual' do
20
+ fake_config = mock
21
+ fake_config.should_receive(:virtual=).with(true)
22
+ Store.stub(:new).and_return(fake_config)
23
+
24
+ Mariner.configure {}
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ module Mariner
4
+
5
+ describe Helper, :type => :helper do
6
+
7
+ before do
8
+ Mariner.configure do
9
+ a_group do
10
+ root_path "A Thing!"
11
+ a_sub_group {}
12
+ end
13
+ end
14
+ end
15
+
16
+ after { Mariner.configuration = nil }
17
+
18
+ describe "#render_navigation" do
19
+
20
+ context "with no args" do
21
+
22
+ it "renders the root level configuration" do
23
+ Mariner.configuration.should_receive(:render)
24
+ render_navigation
25
+ end
26
+
27
+ end
28
+
29
+ context "with one arg" do
30
+
31
+ it "renders the correct configuration" do
32
+ Mariner.configuration.should_not_receive(:render)
33
+ Mariner.configuration.a_group.should_receive(:render)
34
+ render_navigation :a_group
35
+ end
36
+
37
+ end
38
+
39
+ context "with two args" do
40
+
41
+ context "when renderer is a symbol" do
42
+
43
+ it "gets its renderer from Mariner.rendering_strategies" do
44
+ fake_renderer = stub(:render => nil)
45
+ Mariner.rendering_strategies[:fake] = fake_renderer
46
+
47
+ Mariner.configuration.should_not_receive(:render)
48
+ Mariner.configuration.a_group.should_receive(:render).with(fake_renderer)
49
+
50
+ render_navigation :a_group, :fake
51
+ end
52
+
53
+ it "raises an error if the specified renderer doesn't exist" do
54
+ Mariner.rendering_strategies[:nonexistent] = nil
55
+ expect {
56
+ render_navigation :a_group, :nonexistent
57
+ }.to raise_error /rendering strategy not found/i
58
+ end
59
+
60
+ end
61
+
62
+ context "when renderer is not a symbol" do
63
+
64
+ it "renders with a specified strategy" do
65
+ fake_renderer = stub(:render => nil)
66
+ Mariner.configuration.should_not_receive(:render)
67
+ Mariner.configuration.a_group.should_receive(:render).with(fake_renderer)
68
+
69
+ render_navigation :a_group, fake_renderer
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
76
+ context "with a string path" do
77
+
78
+ it "calls through to #render properly" do
79
+ Mariner.configuration.should_not_receive(:render)
80
+ Mariner.configuration.a_group.should_receive(:render)
81
+
82
+ render_navigation "a_group"
83
+ end
84
+
85
+ end
86
+
87
+ context "with a deep string path" do
88
+
89
+ it "calls through to #render properly" do
90
+ Mariner.configuration.should_not_receive(:render)
91
+ Mariner.configuration.a_group.should_not_receive(:render)
92
+ Mariner.configuration.a_group.a_sub_group.should_receive(:render)
93
+ render_navigation "a_group/a_sub_group"
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ describe "#render_navigations" do
101
+
102
+ before do
103
+ Mariner.configure do
104
+ root_path "Top"
105
+
106
+ group_one do
107
+ root_path "One"
108
+ end
109
+
110
+ group_two do
111
+ root_path "Two"
112
+ end
113
+ end
114
+ end
115
+
116
+ it "renders the navigations within the target group without rendering the target itself" do
117
+ Mariner.configuration.should_not_receive(:render)
118
+
119
+ Mariner.configuration.configurations.each do |c|
120
+ _, entity = c
121
+ entity.should_receive(:render)
122
+ end
123
+
124
+ render_navigations
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ end