mariner 0.0.2

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