ducktrails 0.0.1 → 0.0.3
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.
- checksums.yaml +4 -4
- data/README.md +43 -0
- data/app/views/ducktrails/_breadcrumber.html.erb +3 -1
- data/lib/ducktrails.rb +1 -0
- data/lib/ducktrails/config.rb +9 -19
- data/lib/ducktrails/link_collection.rb +31 -79
- data/lib/ducktrails/resource.rb +116 -0
- data/lib/ducktrails/tags.rb +2 -2
- data/lib/ducktrails/utils/deep_struct.rb +18 -0
- data/lib/ducktrails/version.rb +1 -1
- data/lib/ducktrails/view_helpers.rb +5 -14
- metadata +20 -5
- data/app/config/routes.rb +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df465aae8845fba74f32ed951aee5d7cc9509e36
|
4
|
+
data.tar.gz: 0b7fc7bd4dd5b4a8241e398bad0e6760ed2d754b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7c3328e29ba82ee8bab8d429d68d2db8649bc26857f7d163296967756ce6a9060fd9ad893a32314f7147b921da96af628b48a238cfe90114c09bf89b4b659d4
|
7
|
+
data.tar.gz: '02539d3d583729da538d27cefe76c9f83bb1fbc580bf8d8b782cef98cb1f85f7b4e4921ba3c2abfbf6ea895a0cf99efee4ce79cbc0f4aaecce8fed8b51da984c'
|
data/README.md
CHANGED
@@ -10,11 +10,40 @@ Automatically generates breadcrumbs based on routes.
|
|
10
10
|
For basic applications using a restful architecture, ducktrails should generate the breadcrumbs automatically.
|
11
11
|
|
12
12
|
For example, if the uri is `/users/#{user-id}/posts/#{post-id}`
|
13
|
+
Putting `<%= breadcrumbs %>` in the (erb) view.
|
13
14
|
|
14
15
|
Would render:
|
15
16
|
|
16
17
|
`Home / All Users / user-name / All Posts / post-name`
|
17
18
|
|
19
|
+
Simply include `= breadcrumbs` in your view. `breadcrumbs` takes a block argument. The block isn't required but if you want to set some objects for the uri resources (recommend) you can follow the guide below.
|
20
|
+
|
21
|
+
Resources are inferred and will be used to manipulate each URI segment. If a resource is not found, Ducktrails will fallback to the URI.
|
22
|
+
|
23
|
+
Possible keys for a breadcrumb resource are: `resource, key, policy, collection_prefix`. The key and collection prefix can be configured in the `Ducktrails.rb` initializer described below.
|
24
|
+
|
25
|
+
```erb
|
26
|
+
<%=breadcrumbs do
|
27
|
+
{
|
28
|
+
users: {
|
29
|
+
resource: @user,
|
30
|
+
key: :first_name,
|
31
|
+
collection_prefix: 'Some'
|
32
|
+
}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
%>
|
36
|
+
```
|
37
|
+
|
38
|
+
Will output
|
39
|
+
|
40
|
+
Action | Breadcrumb
|
41
|
+
---|---
|
42
|
+
index |`Home / Some Users`
|
43
|
+
show |`Home / Some Users / Kevin`
|
44
|
+
new |`Home / Some Users / New`
|
45
|
+
edit |`Home /Some Users / Kevin / edit`
|
46
|
+
|
18
47
|
## Installation
|
19
48
|
Add this line to your application's Gemfile:
|
20
49
|
|
@@ -32,6 +61,20 @@ Or install it yourself as:
|
|
32
61
|
$ gem install ducktrails
|
33
62
|
```
|
34
63
|
|
64
|
+
## Configuration
|
65
|
+
Ducktrails provides a standard initializer file:
|
66
|
+
**Note**: Currently `ducktrails` is the only template theme provided
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Ducktrails.configure do |config|
|
70
|
+
config.root_path = '/'
|
71
|
+
config.home_name = 'Home'
|
72
|
+
config.collection_prefix = 'All'
|
73
|
+
config.default_key = :id
|
74
|
+
config.theme = 'ducktrails'
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
35
78
|
## Contributing
|
36
79
|
|
37
80
|
Please refer to each project's style guidelines and guidelines for submitting patches and additions. In general, we follow the "fork-and-pull" Git workflow.
|
@@ -1,7 +1,9 @@
|
|
1
1
|
<div class="breadcrumb-container">
|
2
2
|
<div class="container">
|
3
3
|
<ol class="breadcrumb">
|
4
|
-
<li
|
4
|
+
<li>
|
5
|
+
<%= link_to(Ducktrails.config.home_name, Ducktrails.config.root_path) %>
|
6
|
+
</li>
|
5
7
|
<% links.each do |link| %>
|
6
8
|
<%= render partial: 'ducktrails/crumb_link', locals: { link: link } %>
|
7
9
|
<% end %>
|
data/lib/ducktrails.rb
CHANGED
data/lib/ducktrails/config.rb
CHANGED
@@ -11,34 +11,24 @@ module Ducktrails
|
|
11
11
|
|
12
12
|
# Global settings for Ducktrails
|
13
13
|
def self.config
|
14
|
-
@config
|
14
|
+
@config ||= self.config
|
15
15
|
end
|
16
16
|
|
17
|
-
class Configuration
|
17
|
+
class Configuration
|
18
18
|
include ActiveSupport::Configurable
|
19
|
-
config_accessor :root_path
|
20
|
-
config_accessor :home_name
|
21
|
-
# TODO: How to set this?
|
22
|
-
config_accessor :collection_prefix
|
23
|
-
config_accessor :default_key
|
24
|
-
config_accessor :fallback_to_uri
|
25
|
-
config
|
26
|
-
def param_name
|
27
|
-
config.param_name.respond_to?(:call) ? config.param_name.call : config.param_name
|
28
|
-
end
|
29
19
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
config_accessor :theme,
|
21
|
+
:home_name,
|
22
|
+
:root_path,
|
23
|
+
:collection_prefix,
|
24
|
+
:default_key
|
34
25
|
end
|
35
26
|
|
36
|
-
# this is ugly. why can't we pass the default value to config_accessor...?
|
37
27
|
configure do |config|
|
38
|
-
config.
|
28
|
+
config.theme = 'ducktrails'
|
39
29
|
config.home_name = 'Home'
|
30
|
+
config.root_path = '/'
|
40
31
|
config.collection_prefix = 'All'
|
41
32
|
config.default_key = :id
|
42
|
-
config.fallback_to_uri = true
|
43
33
|
end
|
44
34
|
end
|
@@ -3,108 +3,60 @@ require 'concerns/configurable'
|
|
3
3
|
DEFAULTS = {
|
4
4
|
key: :name,
|
5
5
|
policy: true
|
6
|
-
}
|
6
|
+
}.freeze
|
7
7
|
|
8
8
|
module Ducktrails
|
9
|
-
class LinkCollection
|
9
|
+
class LinkCollection
|
10
10
|
include Configurable
|
11
11
|
|
12
|
-
attr_accessor :resources, :current_uri
|
12
|
+
attr_accessor :resources, :current_uri
|
13
13
|
|
14
|
-
def initialize(resources, current_uri
|
15
|
-
@resources ||=
|
14
|
+
def initialize(resources, current_uri)
|
15
|
+
@resources ||= default_resources(resources)
|
16
16
|
@current_uri ||= current_uri
|
17
|
-
@request ||= request
|
18
17
|
end
|
19
18
|
|
20
19
|
def links
|
21
|
-
build_links
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
# All the logic to build the links
|
27
|
-
# Should render an index & show if action is show
|
28
|
-
# Should render an index & new/edit if action is new/edit
|
29
|
-
# Should render an index as _current_page if action is index
|
30
|
-
# If resources are empty, generate breadcrumbs from the uri?
|
31
|
-
def build_links
|
32
|
-
return generate_uri_breadcrumbs if Ducktrails.config.fallback_to_uri
|
33
|
-
raise 'Please provide block configuration for breadcrumbs.'
|
34
|
-
end
|
35
|
-
|
36
|
-
def generate_uri_breadcrumbs
|
37
20
|
split_uri.inject([]) do |links, uri_resource|
|
38
|
-
links <<
|
21
|
+
links << Resource.new(resources, uri_resource, current_uri)
|
22
|
+
.link
|
39
23
|
end.compact
|
40
24
|
end
|
41
25
|
|
42
|
-
|
43
|
-
{
|
44
|
-
text: text,
|
45
|
-
uri: uri
|
46
|
-
}
|
47
|
-
end
|
26
|
+
private
|
48
27
|
|
49
|
-
def
|
50
|
-
|
51
|
-
if resources[uri_segment.to_sym].present?
|
52
|
-
return unless resources[uri_segment.to_sym][:policy]
|
53
|
-
# Build resource link
|
54
|
-
# example /institutions/:institution_id/iterations/:id
|
55
|
-
build_link(text_link(resources[uri_segment.to_sym], index.odd?), split_uri[0..index].join('/').prepend('/'))
|
56
|
-
elsif index.odd? && resources[split_uri[index - 1].to_sym].present?
|
57
|
-
return unless resources[split_uri[index - 1].to_sym][:policy]
|
58
|
-
build_link(text_link(resources[split_uri[index - 1].to_sym], index.odd?), split_uri[0..index].join('/').prepend('/'))
|
59
|
-
else
|
60
|
-
# build links based off of uri
|
61
|
-
build_link(text_link(uri_segment.underscore.humanize, index.odd?), split_uri[0..index].join('/').prepend('/'))
|
62
|
-
end
|
28
|
+
def split_uri
|
29
|
+
@split_uri ||= current_uri.split('/').reject(&:empty?)
|
63
30
|
end
|
64
31
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
return request_pattern[:controller].split('/').last.underscore.humanize.pluralize.prepend(collection_prefix)
|
74
|
-
end
|
75
|
-
|
76
|
-
if show_action
|
77
|
-
return resource[:resource].send(resource[:key])
|
78
|
-
else
|
79
|
-
return resourcer(resource)
|
32
|
+
def default_resources(yield_resources = nil)
|
33
|
+
return {} if yield_resources.nil?
|
34
|
+
yield_resources.inject({}) do |resources, resource|
|
35
|
+
resource[1].assert_valid_keys(Ducktrails::VALID_RESOURCES)
|
36
|
+
@resource = resource[1]
|
37
|
+
resources.merge(
|
38
|
+
resource[0] => DEFAULTS.merge(sanitized_resource)
|
39
|
+
)
|
80
40
|
end
|
81
41
|
end
|
82
42
|
|
83
|
-
|
84
|
-
|
85
|
-
resource.
|
86
|
-
|
87
|
-
|
88
|
-
def request_pattern
|
89
|
-
@request_pattern ||= Rails.application.routes.recognize_path(current_uri)
|
90
|
-
end
|
91
|
-
|
92
|
-
def split_uri
|
93
|
-
@split_uri ||= current_uri.split('/').reject(&:empty?)
|
43
|
+
# Used to filter and sanitize decorated resources
|
44
|
+
def sanitized_resource
|
45
|
+
return @resource if @resource.is_a?(String)
|
46
|
+
return unobjectified_resource if resource_decorated?
|
47
|
+
@resource
|
94
48
|
end
|
95
49
|
|
96
|
-
def
|
97
|
-
@
|
50
|
+
def unobjectified_resource
|
51
|
+
@resource[:resource] = @resource[:resource].object
|
52
|
+
@resource
|
98
53
|
end
|
99
54
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
}
|
106
|
-
)
|
107
|
-
end
|
55
|
+
def resource_decorated?
|
56
|
+
@resource[:resource].respond_to?(:decorated?) &&
|
57
|
+
@resource[:resource].decorated?
|
58
|
+
rescue NoMethodError
|
59
|
+
@resource
|
108
60
|
end
|
109
61
|
end
|
110
62
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require_relative 'utils/deep_struct'
|
2
|
+
# TODO: below
|
3
|
+
# 1. Too many conflicting name-spaces remaining from the first version
|
4
|
+
# 2. Class doesn't do 1 thing
|
5
|
+
# 3. Some redundancy between methods
|
6
|
+
|
7
|
+
module Ducktrails
|
8
|
+
class Resource
|
9
|
+
include Configurable
|
10
|
+
|
11
|
+
attr_reader :resources, :resource, :current_uri
|
12
|
+
|
13
|
+
def initialize(resources, resource, current_uri)
|
14
|
+
@resources = DeepStruct.new(resources)
|
15
|
+
@resource = resource
|
16
|
+
@current_uri = current_uri
|
17
|
+
end
|
18
|
+
|
19
|
+
def link
|
20
|
+
# Build resource link
|
21
|
+
# example /institutions/:institution_id/iterations/:id
|
22
|
+
if resources.send(resource.to_sym).present?
|
23
|
+
return unless resources[resource.to_sym][:policy]
|
24
|
+
build_link(link_text(resources[resource.to_sym]))
|
25
|
+
elsif (show_action || new_action || edit_action) && current_resource.present?
|
26
|
+
return unless current_resource[:policy]
|
27
|
+
build_link(link_text(current_resource))
|
28
|
+
else
|
29
|
+
# build links based off of uri
|
30
|
+
build_link(link_text(resource.underscore.titleize))
|
31
|
+
end
|
32
|
+
rescue ActionController::RoutingError
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def split_uri
|
39
|
+
@split_uri ||= current_uri.split('/').reject(&:empty?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def uri_index
|
43
|
+
split_uri.index(resource)
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: Refactor this to use a resource given by the view_helpers?
|
47
|
+
def current_resource
|
48
|
+
resources[request_pattern[:controller].split('/').last.to_sym]
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_link(text)
|
52
|
+
{
|
53
|
+
text: text,
|
54
|
+
uri: progressive_uri
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def progressive_uri
|
59
|
+
split_uri[0..uri_index].join('/').prepend('/')
|
60
|
+
end
|
61
|
+
|
62
|
+
def link_text(resource)
|
63
|
+
return string_resource if resource.is_a?(String)
|
64
|
+
|
65
|
+
if resource.nil? || resource.resource.nil?
|
66
|
+
return resource.as.prepend(col_prefix) unless resource.resource.present? || resource.as.nil?
|
67
|
+
return controller_resource_title
|
68
|
+
end
|
69
|
+
|
70
|
+
case request_pattern(progressive_uri)[:action]
|
71
|
+
when 'show'
|
72
|
+
resource[:resource].send(resource[:key])
|
73
|
+
when 'edit'
|
74
|
+
'Edit ' + resource[:resource].send(resource[:key])
|
75
|
+
when 'new'
|
76
|
+
'New ' + resourcer(resource.try(:resource)).singularize
|
77
|
+
else
|
78
|
+
resourcer(resource.try(:resource))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def string_resource
|
83
|
+
return resource.titleize if show_action || edit_action || new_action
|
84
|
+
resource.titleize.prepend(col_prefix)
|
85
|
+
end
|
86
|
+
|
87
|
+
def resourcer(resource)
|
88
|
+
resource.class.name.split('::').first.underscore.titleize.pluralize.prepend(col_prefix)
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_pattern(url = nil)
|
92
|
+
@request_pattern ||= Rails.application.routes.recognize_path(url || current_uri)
|
93
|
+
end
|
94
|
+
|
95
|
+
def controller_resource_title
|
96
|
+
return string_resource if request_pattern[:controller].split('/')[uri_index].nil?
|
97
|
+
request_pattern[:controller].split('/')[uri_index].underscore.titleize.pluralize.
|
98
|
+
prepend(col_prefix)
|
99
|
+
end
|
100
|
+
|
101
|
+
def col_prefix
|
102
|
+
return '' if new_action || edit_action
|
103
|
+
if resources[resource]&.collection_prefix.present?
|
104
|
+
resources[resource].collection_prefix.concat(' ')
|
105
|
+
else
|
106
|
+
collection_prefix
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
%w(index show edit new).each do |action_name|
|
111
|
+
define_method("#{action_name}_action") do
|
112
|
+
request_pattern(progressive_uri)[:action].eql?(action_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/ducktrails/tags.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
class DeepStruct < OpenStruct
|
2
|
+
def initialize(hash = nil)
|
3
|
+
@table = {}
|
4
|
+
@hash_table = {}
|
5
|
+
|
6
|
+
return unless hash
|
7
|
+
hash.each do |k, v|
|
8
|
+
@table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
|
9
|
+
@hash_table[k.to_sym] = v
|
10
|
+
|
11
|
+
new_ostruct_member(k)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
@hash_table
|
17
|
+
end
|
18
|
+
end
|
data/lib/ducktrails/version.rb
CHANGED
@@ -5,23 +5,18 @@ require 'ducktrails/tags'
|
|
5
5
|
module Ducktrails
|
6
6
|
module ViewHelpers
|
7
7
|
class Breadcrumber < Tag
|
8
|
-
|
9
8
|
def initialize(options = {})
|
10
9
|
@template = options[:template]
|
11
|
-
@output_buffer = ActionView::OutputBuffer.new
|
12
|
-
end
|
13
|
-
|
14
|
-
def render(&block)
|
15
|
-
@output_buffer
|
16
10
|
end
|
17
11
|
end
|
18
12
|
|
19
|
-
def breadcrumbs(
|
20
|
-
|
13
|
+
def breadcrumbs(&block)
|
14
|
+
resources = build_resource(&block)
|
15
|
+
ducktrail_render(LinkCollection.new(resources, current_uri).links)
|
21
16
|
end
|
22
17
|
|
23
|
-
def
|
24
|
-
|
18
|
+
def build_resource(&block)
|
19
|
+
block_given? ? yield : nil
|
25
20
|
end
|
26
21
|
|
27
22
|
private
|
@@ -30,10 +25,6 @@ module Ducktrails
|
|
30
25
|
request.fullpath
|
31
26
|
end
|
32
27
|
|
33
|
-
def current_request
|
34
|
-
request
|
35
|
-
end
|
36
|
-
|
37
28
|
def current_action
|
38
29
|
Rails.application.routes.recognize_path(current_uri)[:action]
|
39
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ducktrails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Brown
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: factory_girl_rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,21 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: mocha
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
@@ -62,7 +76,6 @@ files:
|
|
62
76
|
- MIT-LICENSE
|
63
77
|
- README.md
|
64
78
|
- Rakefile
|
65
|
-
- app/config/routes.rb
|
66
79
|
- app/views/ducktrails/_breadcrumber.html.erb
|
67
80
|
- app/views/ducktrails/_crumb_link.html.erb
|
68
81
|
- lib/ducktrails.rb
|
@@ -70,7 +83,9 @@ files:
|
|
70
83
|
- lib/ducktrails/config.rb
|
71
84
|
- lib/ducktrails/engine.rb
|
72
85
|
- lib/ducktrails/link_collection.rb
|
86
|
+
- lib/ducktrails/resource.rb
|
73
87
|
- lib/ducktrails/tags.rb
|
88
|
+
- lib/ducktrails/utils/deep_struct.rb
|
74
89
|
- lib/ducktrails/version.rb
|
75
90
|
- lib/ducktrails/view_helpers.rb
|
76
91
|
homepage: http://github.com/proctoru/ducktrails
|
data/app/config/routes.rb
DELETED