breadcrumby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE +21 -0
  4. data/README.md +221 -0
  5. data/lib/breadcrumby.rb +11 -0
  6. data/lib/breadcrumby/engine.rb +15 -0
  7. data/lib/breadcrumby/helpers/view_helper.rb +9 -0
  8. data/lib/breadcrumby/models/extension.rb +74 -0
  9. data/lib/breadcrumby/models/home.rb +23 -0
  10. data/lib/breadcrumby/models/viewer.rb +154 -0
  11. data/lib/breadcrumby/version.rb +5 -0
  12. data/spec/factories/course.rb +9 -0
  13. data/spec/factories/grade.rb +10 -0
  14. data/spec/factories/level.rb +9 -0
  15. data/spec/factories/school.rb +7 -0
  16. data/spec/factories/unit.rb +9 -0
  17. data/spec/lib/breadcrumby/helpers/breadcrumby_spec.rb +22 -0
  18. data/spec/lib/breadcrumby/models/extension/breadcrumby_options_spec.rb +89 -0
  19. data/spec/lib/breadcrumby/models/extension/breadcrumby_spec.rb +126 -0
  20. data/spec/lib/breadcrumby/models/home/name_spec.rb +25 -0
  21. data/spec/lib/breadcrumby/models/home/show_path_spec.rb +25 -0
  22. data/spec/lib/breadcrumby/models/viewer/breadcrumb_spec.rb +55 -0
  23. data/spec/lib/breadcrumby/models/viewer/breadcrumbs_spec.rb +25 -0
  24. data/spec/lib/breadcrumby/models/viewer/current_object_spec.rb +68 -0
  25. data/spec/lib/breadcrumby/models/viewer/i18n_action_name_spec.rb +28 -0
  26. data/spec/lib/breadcrumby/models/viewer/i18n_name_spec.rb +53 -0
  27. data/spec/lib/breadcrumby/models/viewer/item_options_spec.rb +17 -0
  28. data/spec/lib/breadcrumby/models/viewer/item_spec.rb +30 -0
  29. data/spec/lib/breadcrumby/models/viewer/link_action_spec.rb +23 -0
  30. data/spec/lib/breadcrumby/models/viewer/link_options_spec.rb +49 -0
  31. data/spec/lib/breadcrumby/models/viewer/link_spec.rb +40 -0
  32. data/spec/lib/breadcrumby/models/viewer/link_tag_name_options_spec.rb +13 -0
  33. data/spec/lib/breadcrumby/models/viewer/link_tag_name_spec.rb +35 -0
  34. data/spec/lib/breadcrumby/models/viewer/list_options_spec.rb +17 -0
  35. data/spec/lib/breadcrumby/models/viewer/meta_spec.rb +14 -0
  36. data/spec/lib/breadcrumby/models/viewer/object_extra_spec.rb +80 -0
  37. data/spec/lib/breadcrumby/models/viewer/scope_spec.rb +35 -0
  38. data/spec/rails_helper.rb +11 -0
  39. data/spec/support/common.rb +22 -0
  40. data/spec/support/database_cleaner.rb +21 -0
  41. data/spec/support/db/migrate/course.rb +11 -0
  42. data/spec/support/db/migrate/grade.rb +12 -0
  43. data/spec/support/db/migrate/level.rb +11 -0
  44. data/spec/support/db/migrate/school.rb +9 -0
  45. data/spec/support/db/migrate/unit.rb +11 -0
  46. data/spec/support/factory_girl.rb +9 -0
  47. data/spec/support/html_matchers.rb +7 -0
  48. data/spec/support/migrate.rb +9 -0
  49. data/spec/support/models/course.rb +5 -0
  50. data/spec/support/models/grade.rb +6 -0
  51. data/spec/support/models/level.rb +5 -0
  52. data/spec/support/models/school.rb +15 -0
  53. data/spec/support/models/unit.rb +13 -0
  54. data/spec/support/shoulda.rb +10 -0
  55. metadata +321 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4a1a44d7813b238cba249579dd22ea79f7c4cc26
4
+ data.tar.gz: 81d23babac7e3dd6de67f94fa49c1b2801f98fc9
5
+ SHA512:
6
+ metadata.gz: 758338710ffca12ef1a421fa172b765b964d87589d08e8c84ee96c2466f8cb397ea44790b2850f82687c2f45517048748c8ed86f8175a6552cf1a78222ee7875
7
+ data.tar.gz: 5f39e94f2d620da90f97519283b20a942ca662851e71baacc9519baa275922578ca289c4819551548cb6723f9b0c7bcf6c60a48df04a0eaf6e52c26e3e1e659a
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # 0.1.0
2
+
3
+ - Automatic adition of contextual link before action crumbs;
4
+ - Custom contextual link URL via model method;
5
+ - Custom link text via model method or via I18n.
6
+ - Custom link title via I18n with dinamic argument.
7
+ - Custom link URL via model method;
8
+ - Custom links via model method.
9
+ - Generation of breadcrumb based on path navigation;
10
+ - I18n to customize actions crumb;
11
+ - Render aditional crumb based on some action;
12
+ - Semantic Breadcrumb HTML from Google.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Washington Botelho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # Breadcrumby
2
+
3
+ [![Build Status](https://travis-ci.org/wbotelhos/breadcrumby.svg)](https://travis-ci.org/wbotelhos/breadcrumby)
4
+
5
+ A solid Breadcrumb for Rails.
6
+ You do not need to dirty your controllers with a bunch of code.
7
+ Breadcrumby is a really relational breadcrumb.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ gem install breadcrumby
13
+ ```
14
+
15
+ Or add the following code on your `Gemfile`:
16
+
17
+ ```ruby
18
+ gem 'breadcrumby'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ With the following example of `ActiveRecord` relations:
24
+
25
+ ```ruby
26
+ class School
27
+ def show_path
28
+ "/schools/#{id}"
29
+ end
30
+ end
31
+ ```
32
+
33
+ ```ruby
34
+ class Course
35
+ belongs_to :school
36
+
37
+ def show_path
38
+ "/courses/#{id}"
39
+ end
40
+ end
41
+ ```
42
+
43
+ Let's make it know about the breadcrumb path:
44
+
45
+ ```ruby
46
+ class School
47
+ breadcrumby
48
+
49
+ def show_path
50
+ "/schools/#{id}"
51
+ end
52
+ end
53
+ ```
54
+
55
+ Now school knows how to buid the breadcrumb but since it has no `path` it will be the last item.
56
+
57
+ So, we need to teach the Course class how to follow the path until School:
58
+
59
+ ```ruby
60
+ class Course
61
+ breadcrumby path: :school
62
+
63
+ belongs_to :school
64
+
65
+ def show_path
66
+ "/courses/#{id}"
67
+ end
68
+ end
69
+ ```
70
+
71
+ Now Breadcrumby know how to fetch the path, using the `belongs_to` relation.
72
+
73
+ ## View
74
+
75
+ With a instance of Course that has a relation with School, we can build the breadcrumb:
76
+
77
+ ```ruby
78
+ <%= breadcrumby @course %>
79
+ ```
80
+
81
+ And the result will be: `Home / School / Course`
82
+
83
+ ## HTML
84
+
85
+ Breadcrumby uses the [semantic Breadcrumb HTML from Google](https://developers.google.com/search/docs/data-types/breadcrumbs):
86
+
87
+ ```html
88
+ <ol class="breadcrumby" itemscope itemtype="http://schema.org/BreadcrumbList">
89
+ <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
90
+ <a itemprop="item" itemscope itemtype="http://schema.org/Thing" title="{name}" href="{show_path}">
91
+ <span itemprop="name">{name}</span>
92
+ </a>
93
+
94
+ <meta content="1" itemprop="position">
95
+ </li>
96
+ </ol>
97
+ ```
98
+
99
+ - `name`: Fetched from method `name` of the model;
100
+ - `show_path`: Fetched from method `show_path` of the model.
101
+
102
+ > [I18n](I18n) configuration will always has priority over the model method.
103
+
104
+ ## Home
105
+
106
+ As you could see, the Home crumb was generated automatically.
107
+ You can customize the name with `I18n` and the `show_path` will be `root_path` or `/` by default.
108
+
109
+ ## Action
110
+
111
+ You can add one last path on breadcrumb to indicate the current action you are doing, like a edition:
112
+
113
+ ```ruby
114
+ class School
115
+ breadcrumby action: :edit
116
+ end
117
+ ```
118
+
119
+ It generates a muted link on the end: `Home / School / Edition`
120
+
121
+ ```ruby
122
+ <ol class="breadcrumby" itemscope itemtype="http://schema.org/BreadcrumbList">
123
+ <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
124
+ <a itemprop="item" itemscope itemtype="http://schema.org/Thing" title="Edition" href="javascript:void(0);">
125
+ <span itemprop="name">Edition</span>
126
+ </a>
127
+
128
+ <meta content="3" itemprop="position">
129
+ </li>
130
+ </ol>
131
+ ```
132
+
133
+ All actions without `actions` option will try to follow `path` options.
134
+ If it is a new object, it will have no relation and will raise error.
135
+
136
+ ### Custom Action Path
137
+
138
+ For actions like `new` where the object will be a `new_record`, we can customize the candidate to represent the self object.
139
+ This way we can build a minimum path, not just `Home / New`.
140
+ Let's say you have `Unit` on session and wants to set it on breadcrumb to say you are creating a `Course` on that unit of your school:
141
+
142
+ ```ruby
143
+ class Course
144
+ breadcrumby actions: {
145
+ new: -> (view) { Unit.find(view.session[:current_school][:id])
146
+ }
147
+ end
148
+ ```
149
+
150
+ Now the `self` object will be the `new` call result and the output will be:
151
+
152
+ ```ruby
153
+ Home / School / Unit / Courses / New
154
+ ```
155
+
156
+ As you can see, the path will be completed from the `self` (new result) object.
157
+ Plus, since the new object is not `Unit`, we need a context. It will be related with original model with value of `index_path` as path:
158
+
159
+ ```ruby
160
+ <a itemprop="item" itemscope="itemscope" itemtype="http://schema.org/Thing" title="List Courses" href="/courses">
161
+ <span itemprop="name">Turmas</span>
162
+ </a>
163
+ ```
164
+
165
+ Now it is possible to navigate to collection of items you want to create a new one.
166
+
167
+ ## I18n
168
+
169
+ You can customize some attributes via I18n to be fast and global:
170
+
171
+ ```yaml
172
+ en-US:
173
+ breadcrumby:
174
+ home:
175
+ name: Home
176
+ title: Home Page
177
+
178
+ school:
179
+ name: School
180
+ title: "School: %{name}"
181
+
182
+ actions:
183
+ edit: # new / index / method_name ...
184
+ name: Edition
185
+ title: "Editing: %{name}"
186
+ ```
187
+
188
+ - `actions`: Properties to change the actions crumb;
189
+ - `name`: The name of the crumb item;
190
+ - `title`: The title of the crum item with possibility of to use the name `%{name}`.
191
+
192
+ You can change the model key name, since the default search is the class method name:
193
+
194
+ ```ruby
195
+ class School
196
+ breadcrumby i18n_key: :school_key
197
+ end
198
+ ```
199
+
200
+ And now use:
201
+
202
+ ```yaml
203
+ en-US:
204
+ breadcrumby:
205
+ school_key:
206
+ name: School
207
+ ```
208
+
209
+ To make translations more generic just take it of inside the model name and it will be used for all models:
210
+
211
+ ```yaml
212
+ en-US:
213
+ breadcrumby:
214
+ actions:
215
+ edit:
216
+ name: Edition
217
+ ```
218
+
219
+ ## Love it!
220
+
221
+ Via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=X8HEP2878NDEG&item_name=breadcrumby) or [Gittip](http://www.gittip.com/wbotelhos). Thanks! (:
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadcrumby
4
+ end
5
+
6
+ require 'breadcrumby/engine'
7
+ require 'breadcrumby/models/extension'
8
+ require 'breadcrumby/models/home'
9
+ require 'breadcrumby/models/viewer'
10
+
11
+ ActiveRecord::Base.include Breadcrumby::Extension
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'breadcrumby/helpers/view_helper'
4
+
5
+ module Breadcrumby
6
+ module Rails
7
+ class Engine < ::Rails::Engine
8
+ initializer 'breadcrumby.include_view_helper' do |_app|
9
+ ActiveSupport.on_load :action_view do
10
+ include ViewHelper
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadcrumby
4
+ module ViewHelper
5
+ def breadcrumby(object, options = {})
6
+ Viewer.new(object, options, self).breadcrumb
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadcrumby
4
+ module Extension
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def breadcrumby
9
+ path_option = breadcrumby_options[:path]
10
+
11
+ if !path_option.is_a?(Array) || path_option.blank?
12
+ path_option = [path_option].flatten.compact
13
+ end
14
+
15
+ extracting self, path_option.reverse, [self]
16
+ end
17
+
18
+ def breadcrumby_options
19
+ self.class.breadcrumby_options
20
+ end
21
+
22
+ private
23
+
24
+ def extract(models, index, object, objects)
25
+ if last_item?(models, index)
26
+ objects += object.breadcrumby
27
+ else
28
+ objects << object
29
+ end
30
+
31
+ objects
32
+ end
33
+
34
+ def extracting(object, models, objects)
35
+ return objects if models.blank?
36
+
37
+ models.each.with_index do |model, index|
38
+ if model.is_a? Array
39
+ object = send(model.last)
40
+ index = 0
41
+ objects = extract(model, index, object, objects)
42
+
43
+ unless last_item?(model, index)
44
+ extracting object, model.reverse.drop(1), objects
45
+ end
46
+ elsif !model.nil?
47
+ object = object.send(model)
48
+ objects = extract(models, index, object, objects)
49
+ end
50
+ end
51
+
52
+ objects
53
+ end
54
+
55
+ def last_item?(collection, index)
56
+ collection.size - 1 == index
57
+ end
58
+ end
59
+
60
+ module ClassMethods
61
+ def breadcrumby(options = {})
62
+ @options = options.reverse_merge(
63
+ actions: {},
64
+ i18n_key: name.underscore,
65
+ method_name: :name
66
+ )
67
+ end
68
+
69
+ def breadcrumby_options
70
+ @options || {}
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadcrumby
4
+ class Home
5
+ include Breadcrumby::Extension
6
+
7
+ def initialize(view)
8
+ @view = view
9
+ end
10
+
11
+ breadcrumby i18n_key: :home
12
+
13
+ def name
14
+ I18n.t 'breadcrumby.home.name', default: 'Home'
15
+ end
16
+
17
+ def show_path
18
+ return @view.root_path if @view.respond_to?(:root_path)
19
+
20
+ '/'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadcrumby
4
+ class Viewer
5
+ def initialize(object, options = {}, view = ActionController::Base.helpers)
6
+ @object = object
7
+ @options = options
8
+ @view = view
9
+ end
10
+
11
+ def breadcrumb
12
+ return '' unless @object.respond_to?(:breadcrumby)
13
+
14
+ list = breadcrumbs(current_object).map.with_index do |object, index|
15
+ item link(object) + meta(index + 1)
16
+ end
17
+
18
+ list += object_extra(list.size)
19
+
20
+ @view.content_tag :ol, list.join('').html_safe, list_options
21
+ end
22
+
23
+ def breadcrumbs(object)
24
+ items = object.breadcrumby
25
+ items << Breadcrumby::Home.new(@view)
26
+
27
+ items.reverse
28
+ end
29
+
30
+ def current_object
31
+ @current_object ||= begin
32
+ return @object if object_action.blank?
33
+
34
+ if object_action.respond_to?(:call)
35
+ object_action.call @view
36
+ else
37
+ object_action
38
+ end
39
+ end
40
+ end
41
+
42
+ def i18n_action_name(object, action)
43
+ label = "actions.#{action}.name"
44
+
45
+ I18n.t(label, scope: scope(object), default:
46
+ I18n.t(label, scope: scope(object, include_model: false), default:
47
+ I18n.t(action)))
48
+ end
49
+
50
+ def i18n_name(object)
51
+ I18n.t(:name,
52
+ scope: scope(object),
53
+ default: object.send(object.breadcrumby_options[:method_name]) || '--')
54
+ end
55
+
56
+ def item(content, options = item_options)
57
+ @view.content_tag :li, content, options
58
+ end
59
+
60
+ def item_options
61
+ {
62
+ itemprop: :itemListElement,
63
+ itemscope: true,
64
+ itemtype: 'http://schema.org/ListItem'
65
+ }
66
+ end
67
+
68
+ def link(object, action: nil)
69
+ @view.link_to(
70
+ link_tag_name(object, action),
71
+ link_action(object, action),
72
+ link_options(object, action)
73
+ )
74
+ end
75
+
76
+ def link_action(object, action)
77
+ return object.index_path if action == :index
78
+
79
+ # TODO: get current path to behaves like a refresh on the same page. @view.request.url?
80
+ action ? 'javascript:void(0);' : object.show_path
81
+ end
82
+
83
+ def link_options(object, action)
84
+ name = i18n_name(object)
85
+ title_path = action ? "actions.#{action}.title" : :title
86
+ title = I18n.t(title_path, scope: scope(object), name: name, default:
87
+ I18n.t(title_path, scope: scope(object, include_model: false)))
88
+
89
+ {
90
+ itemprop: :item,
91
+ itemscope: true,
92
+ itemtype: 'http://schema.org/Thing',
93
+ title: title
94
+ }
95
+ end
96
+
97
+ def link_tag_name(object, action)
98
+ name = action ? i18n_action_name(object, action) : i18n_name(object)
99
+
100
+ @view.content_tag :span, name, link_tag_name_options
101
+ end
102
+
103
+ def link_tag_name_options
104
+ { itemprop: :name }
105
+ end
106
+
107
+ def list_options
108
+ {
109
+ class: :breadcrumby,
110
+ itemscope: true,
111
+ itemtype: 'http://schema.org/BreadcrumbList'
112
+ }
113
+ end
114
+
115
+ def meta(index)
116
+ @view.tag :meta, content: index, itemprop: :position
117
+ end
118
+
119
+ def object_extra(index)
120
+ action = @options[:action]
121
+ result = []
122
+
123
+ return result if action.blank?
124
+
125
+ if @object != current_object
126
+ index += 1
127
+
128
+ result << item(link(@object, action: :index) + meta(index))
129
+ end
130
+
131
+ result << item(link(current_object, action: action) + meta(index + 1))
132
+
133
+ result
134
+ end
135
+
136
+ def scope(object, include_model: true)
137
+ result = [:breadcrumby]
138
+
139
+ result << object.breadcrumby_options[:i18n_key] if include_model
140
+
141
+ result
142
+ end
143
+
144
+ private
145
+
146
+ def object_action
147
+ @object_action ||= object_options[:actions][@options[:action]]
148
+ end
149
+
150
+ def object_options
151
+ @object.breadcrumby_options
152
+ end
153
+ end
154
+ end