gretel-trails 0.0.1 → 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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/README.md +120 -10
- data/gretel-trails.gemspec +3 -1
- data/lib/assets/javascripts/gretel.trails.hidden.js.coffee.erb +3 -3
- data/lib/generators/gretel/USAGE +8 -0
- data/lib/generators/gretel/install_generator.rb +19 -0
- data/lib/generators/gretel/templates/breadcrumbs.rb +28 -0
- data/lib/generators/gretel/templates/trail_migration.rb +11 -0
- data/lib/generators/gretel/trail/migration_generator.rb +20 -0
- data/lib/gretel/trails.rb +79 -0
- data/lib/gretel/trails/patches.rb +3 -0
- data/lib/gretel/trails/patches/gretel.rb +11 -0
- data/lib/gretel/trails/patches/renderer.rb +17 -0
- data/lib/gretel/trails/patches/view_helpers.rb +6 -0
- data/lib/gretel/trails/stores.rb +4 -0
- data/lib/gretel/trails/stores/active_record_store.rb +64 -0
- data/lib/gretel/trails/stores/redis_store.rb +54 -0
- data/lib/gretel/trails/stores/store.rb +54 -0
- data/lib/gretel/trails/stores/url_store.rb +57 -0
- data/lib/gretel/trails/strategies/hidden_strategy.rb +8 -8
- data/lib/gretel/trails/tasks.rb +5 -0
- data/lib/gretel/trails/version.rb +1 -1
- data/test/dummy/config/breadcrumbs.rb +4 -0
- data/test/dummy/config/initializers/gretel.rb +1 -1
- data/test/dummy/config/routes.rb +2 -0
- data/test/stores/active_record_store_test.rb +53 -0
- data/test/stores/redis_store_test.rb +35 -0
- data/test/stores/url_store_test.rb +38 -0
- data/test/test_helper.rb +1 -0
- data/test/trails_test.rb +25 -0
- data/test/view_helpers_test.rb +41 -0
- metadata +57 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e37b779cdb1997c44fbffe04b0e99cdcaeffa9ac
|
4
|
+
data.tar.gz: d90ed701b34331eae74e4d60ea6f3c465b3841f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5351e3714d5dbaf54f423248c1919876e129b02fab2f2260097b63962f43def0598b485a11ca18596bc0c526387efce9813dbc1f8f07d233de0f6518eb9a0d7
|
7
|
+
data.tar.gz: 0f29f72ea2058d12e67a32408fffe33e40d2040f614968d88c12a0f38ec3eecf86b1c3585860b633e783c0687dff2f03e299d0e590d898b8dd096afd2cfad620
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
# Gretel::Trails
|
4
4
|
|
5
|
-
Gretel::Trails makes it
|
5
|
+
Gretel::Trails makes it possible to set [Gretel](https://github.com/lassebunk/gretel) breadcrumb trails via the URL – `params[:trail]`.
|
6
|
+
This makes it possible to link back to a different breadcrumb trail than the one specified in your breadcrumb, for example if you have a
|
7
|
+
store with products that have a default parent to their category, but when visiting from the reviews section, you want to link back to the reviews instead.
|
8
|
+
|
9
|
+
You can also hide trails from the user using the `:hidden` strategy, so they don't see them in URLs when navigating your site. See below for more info.
|
6
10
|
|
7
11
|
## Installation
|
8
12
|
|
@@ -18,7 +22,43 @@ And run:
|
|
18
22
|
$ bundle
|
19
23
|
```
|
20
24
|
|
21
|
-
|
25
|
+
Gretel::Trails has different stores that are used to serialize and deserialize the trails for use in URLs.
|
26
|
+
|
27
|
+
The default store is the URL store that encodes trails directly in the URL. Note that a trail stored in the URL can get very long, so the recommended way is to use the database or Redis store. See [Stores](#stores) below for more info.
|
28
|
+
|
29
|
+
In order to use the URL store, you must set a secret that's used to prevent cross-site scripting attacks. In an initializer, e.g. *config/initializers/gretel.rb*:
|
30
|
+
|
31
|
+
```
|
32
|
+
Gretel::Trails::UrlStore.secret = 'your_key_here' # Must be changed to something else to be secure
|
33
|
+
```
|
34
|
+
|
35
|
+
You can generate a secret using `SecureRandom.hex(64)` or `rake secret`.
|
36
|
+
|
37
|
+
Then you can set the breadcrumb trail:
|
38
|
+
|
39
|
+
```erb
|
40
|
+
<% breadcrumb :reviews %>
|
41
|
+
...
|
42
|
+
<% @products.each do |product| %>
|
43
|
+
<%= link_to @product.name, product_path(product, trail: breadcrumb_trail) %>
|
44
|
+
<% end %>
|
45
|
+
```
|
46
|
+
|
47
|
+
The product view will now have the breadcrumb trail from the first page (reviews) instead of its default parent.
|
48
|
+
|
49
|
+
## Custom trail param
|
50
|
+
|
51
|
+
The default trail param is `params[:trail]`. You can change it in an initializer:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Gretel::Trails.trail_param = :other_param
|
55
|
+
```
|
56
|
+
|
57
|
+
## Hiding trails in URLs
|
58
|
+
|
59
|
+
Gretel::Trails has a `:hidden` strategy that can be used to hide trails in URLs from the user while the server sees them. This is done via data attributes and `history.replaceState` in browsers that support it.
|
60
|
+
|
61
|
+
To hide trails, you set the strategy in an initializer, e.g. *config/initializers/gretel.rb*:
|
22
62
|
|
23
63
|
```ruby
|
24
64
|
Gretel::Trails.strategy = :hidden
|
@@ -38,11 +78,11 @@ And finally, at the bottom of *app/assets/javascripts/application.js*:
|
|
38
78
|
```
|
39
79
|
|
40
80
|
Breadcrumb trails are now hidden from the user so they don't see them in URLs. It uses data attributes and `history.replaceState` to hide the trails from the URL.
|
41
|
-
For older browsers it falls back gracefully to showing trails in the URL, as specified by `Gretel.trail_param`.
|
81
|
+
For older browsers it falls back gracefully to showing trails in the URL, as specified by `Gretel::Trails.trail_param`.
|
42
82
|
|
43
83
|
Note: If you use [Turbolinks](https://github.com/rails/turbolinks), it's important that you add the require *after* you require Turbolinks. Else it won't work.
|
44
84
|
|
45
|
-
|
85
|
+
### Usage
|
46
86
|
|
47
87
|
When you want to invisibly add the current trail when the user clicks a link, you add a special JS selector to the link where you want the trail added on click:
|
48
88
|
|
@@ -54,9 +94,17 @@ When you want to invisibly add the current trail when the user clicks a link, yo
|
|
54
94
|
|
55
95
|
Trails are now transferred invisibly to the next page when the user clicks a link.
|
56
96
|
|
57
|
-
See Customization below for info on changing the `.js-append-trail` selector.
|
97
|
+
See [Customization](#customization) below for info on changing the `.js-append-trail` selector.
|
98
|
+
|
99
|
+
If you need to set the trail directly on a link without the JS selector, you can do so:
|
58
100
|
|
59
|
-
|
101
|
+
```erb
|
102
|
+
<%= link_to "My Link", my_link_path, data: { trail: breadcrumb_trail } %>
|
103
|
+
```
|
104
|
+
|
105
|
+
See [Customization](#customization) below for info on changing the `data-trail` attribute to something else.
|
106
|
+
|
107
|
+
### Breadcrumb links
|
60
108
|
|
61
109
|
Inside breadcrumbs, the links are automatically transformed with trails removed from the URLs and applied as data attributes instead.
|
62
110
|
If you want to do custom breadcrumb links with these changes applied, you can use the `breadcrumb_link_to` helper:
|
@@ -89,14 +137,76 @@ The default trail data attribute for `<body>` and links is `data-trail` but you
|
|
89
137
|
Gretel::Trails::HiddenStrategy.data_attribute = "other-data-attribute"
|
90
138
|
```
|
91
139
|
|
92
|
-
|
140
|
+
`data-` is added automatically, so if for example you want the attribute to be `data-my-attr`, you just set it to `my-attr`.
|
141
|
+
|
142
|
+
## Stores
|
143
|
+
|
144
|
+
Gretel::Trails comes with different stores for encoding and decoding trails for use in the URL.
|
145
|
+
|
146
|
+
### URL store
|
147
|
+
|
148
|
+
The default store is the URL store which is great for simple use, but if you have longer trails, it can get very long.
|
149
|
+
|
150
|
+
To use the URL store, set it in an initializer, e.g. *config/initializers/gretel.rb*:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
Gretel::Trails.store = :url # Not really needed as this is the default
|
154
|
+
Gretel::Trails::UrlStore.secret = 'your_key_here' # Must be changed to something else to be secure
|
155
|
+
```
|
156
|
+
|
157
|
+
The secret is used to prevent cross-site scripting attacks. You can generate a secure one using `SecureRandom.hex(64)` or `rake secret`.
|
158
|
+
|
159
|
+
### Database store
|
160
|
+
|
161
|
+
The database store stores trails in the database so the trail keys have a maximum length of 40 characters (a SHA1 of the trail).
|
162
|
+
|
163
|
+
To use the database store, set it an initializer, e.g. *config/initializers/gretel.rb*:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
Gretel::Trails.store = :db
|
167
|
+
```
|
168
|
+
|
169
|
+
You also need to create a migration for the database table that holds the trails:
|
170
|
+
|
171
|
+
```bash
|
172
|
+
$ rails generate gretel:trails:migration
|
173
|
+
```
|
174
|
+
|
175
|
+
This creates a table named `gretel_trails` that hold the trails.
|
176
|
+
|
177
|
+
ActiveRecord doesn't delete expired records automatically, so to delete expired trails you need to run the following rake task, for example once daily:
|
178
|
+
|
179
|
+
```bash
|
180
|
+
$ rake gretel:trails:delete_expired
|
181
|
+
```
|
182
|
+
|
183
|
+
You can also run `Gretel::Trails.delete_expired` directly.
|
184
|
+
|
185
|
+
If you need a gem for managing recurring tasks, [Whenever](https://github.com/javan/whenever) is a solution that handles cron jobs via Ruby code.
|
186
|
+
|
187
|
+
The default expiration period is 1 day. To set a custom expiration period, in an initializer:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
Gretel::Trails::ActiveRecordStore.expires_in = 2.days
|
191
|
+
```
|
192
|
+
|
193
|
+
### Redis store
|
194
|
+
|
195
|
+
If you want to store trails in [Redis](https://github.com/redis/redis), you can use the Redis store.
|
196
|
+
|
197
|
+
To use the Redis store, set it in an initializer, e.g. *config/initializers/gretel.rb*:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
Gretel::Trails.store = :redis
|
201
|
+
Gretel::Trails::RedisStore.connect_options = { host: "10.0.1.1", port: 6380 }
|
202
|
+
```
|
93
203
|
|
94
|
-
|
204
|
+
Trails are now stored in Redis and expired automatically after 1 day (by default).
|
95
205
|
|
96
|
-
|
206
|
+
To set a custom expiration period, in an initializer:
|
97
207
|
|
98
208
|
```ruby
|
99
|
-
Gretel.
|
209
|
+
Gretel::Trails::RedisStore.expires_in = 2.days
|
100
210
|
```
|
101
211
|
|
102
212
|
## Requirements
|
data/gretel-trails.gemspec
CHANGED
@@ -17,12 +17,14 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^test/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "gretel", ">= 3.0.0.
|
20
|
+
spec.add_dependency "gretel", ">= 3.0.0.beta5"
|
21
21
|
spec.add_dependency "rails", ">= 3.2.0"
|
22
22
|
spec.add_development_dependency "rails", "~> 3.2.13"
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
24
|
spec.add_development_dependency "sqlite3"
|
25
25
|
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "fakeredis", "~> 0.4.2"
|
27
|
+
spec.add_development_dependency "timecop", "~> 0.6.3"
|
26
28
|
spec.add_development_dependency "capybara", "~> 2.1.0"
|
27
29
|
spec.add_development_dependency "capybara-webkit", "~> 1.0.0"
|
28
30
|
end
|
@@ -8,10 +8,10 @@ end
|
|
8
8
|
|
9
9
|
# Remove trail from querystring
|
10
10
|
removeTrailFromUrl = ->
|
11
|
+
return if location.href.indexOf("<%= Gretel::Trails.trail_param %>=") is -1
|
11
12
|
if history.replaceState?
|
12
|
-
return if location.href.indexOf("<%= Gretel.trail_param %>=") is -1
|
13
13
|
uri = new Gretel.Trails.Uri(location.href)
|
14
|
-
history.replaceState history.state, document.title, uri.deleteQueryParam("<%= Gretel.trail_param %>")
|
14
|
+
history.replaceState history.state, document.title, uri.deleteQueryParam("<%= Gretel::Trails.trail_param %>")
|
15
15
|
|
16
16
|
# Remove trail on load
|
17
17
|
removeTrailFromUrl()
|
@@ -26,7 +26,7 @@ $ ->
|
|
26
26
|
if trail = $(this).data("<%= Gretel::Trails::HiddenStrategy.data_attribute %>") || $("body").data("<%= Gretel::Trails::HiddenStrategy.data_attribute %>")
|
27
27
|
href = $(this).attr("href")
|
28
28
|
uri = new Gretel.Trails.Uri(href)
|
29
|
-
href = uri.deleteQueryParam("<%= Gretel.trail_param %>").addQueryParam("<%= Gretel.trail_param %>", trail)
|
29
|
+
href = uri.deleteQueryParam("<%= Gretel::Trails.trail_param %>").addQueryParam("<%= Gretel::Trails.trail_param %>", trail)
|
30
30
|
$(this).attr("href", href)
|
31
31
|
else
|
32
32
|
console?.log "[Gretel] No `data-<%= Gretel::Trails::HiddenStrategy.data_attribute %>` was found on the <body> tag or the link you just clicked. Please set it using the `breadcrumb_trail` helper or see the Gretel::Trails readme for more info."
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Gretel
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
desc "Creates a sample configuration file in config/breadcrumbs.rb"
|
8
|
+
def create_config_file
|
9
|
+
copy_file "breadcrumbs.rb", "config/breadcrumbs.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Creates an initializer with trail secret"
|
13
|
+
def create_initializer
|
14
|
+
initializer "gretel.rb" do
|
15
|
+
%{Gretel::Trails::UrlStore.secret = '#{SecureRandom.hex(64)}'}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
crumb :root do
|
2
|
+
link "Home", root_path
|
3
|
+
end
|
4
|
+
|
5
|
+
# crumb :projects do
|
6
|
+
# link "Projects", projects_path
|
7
|
+
# end
|
8
|
+
|
9
|
+
# crumb :project do |project|
|
10
|
+
# link project.name, project_path(project)
|
11
|
+
# parent :projects
|
12
|
+
# end
|
13
|
+
|
14
|
+
# crumb :project_issues do |project|
|
15
|
+
# link "Issues", project_issues_path(project)
|
16
|
+
# parent :project, project
|
17
|
+
# end
|
18
|
+
|
19
|
+
# crumb :issue do |issue|
|
20
|
+
# link issue.title, issue_path(issue)
|
21
|
+
# parent :project_issues, issue.project
|
22
|
+
# end
|
23
|
+
|
24
|
+
# If you want to split your breadcrumbs configuration over multiple files, you
|
25
|
+
# can create a folder named `config/breadcrumbs` and put your configuration
|
26
|
+
# files there. All *.rb files (e.g. `frontend.rb` or `products.rb`) in that
|
27
|
+
# folder are loaded and reloaded automatically when you change them, just like
|
28
|
+
# this file (`config/breadcrumbs.rb`).
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateGretelTrails < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :gretel_trails do |t|
|
4
|
+
t.string :key, limit: 40
|
5
|
+
t.text :value
|
6
|
+
t.datetime :expires_at
|
7
|
+
end
|
8
|
+
add_index :gretel_trails, :key, unique: true
|
9
|
+
add_index :gretel_trails, :expires_at
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Gretel
|
4
|
+
module Trail
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
source_root File.expand_path('../../templates', __FILE__)
|
9
|
+
|
10
|
+
def self.next_migration_number(path)
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Creates a migration for a table to store trail data"
|
15
|
+
def create_migration
|
16
|
+
migration_template "trail_migration.rb", "db/migrate/create_gretel_trails.rb"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/gretel/trails.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
require "gretel"
|
2
|
+
require "gretel/trails/stores"
|
3
|
+
require "gretel/trails/tasks"
|
4
|
+
require "gretel/trails/patches"
|
2
5
|
require "gretel/trails/version"
|
3
6
|
require "gretel/trails/engine"
|
4
7
|
|
5
8
|
module Gretel
|
6
9
|
module Trails
|
10
|
+
STORES = {
|
11
|
+
url: UrlStore,
|
12
|
+
db: ActiveRecordStore,
|
13
|
+
redis: RedisStore
|
14
|
+
}
|
15
|
+
|
7
16
|
class << self
|
8
17
|
# Activated strategies
|
9
18
|
def strategies
|
@@ -20,6 +29,76 @@ module Gretel
|
|
20
29
|
end
|
21
30
|
|
22
31
|
alias_method :strategy=, :strategies=
|
32
|
+
|
33
|
+
# Gets the store that is used to encode and decode trails.
|
34
|
+
# Default: +Gretel::Trails::UrlStore+
|
35
|
+
def store
|
36
|
+
@store ||= UrlStore
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the store that is used to encode and decode trails.
|
40
|
+
# Can be a subclass of +Gretel::Trails::Store+, or a symbol: +:url+, +:db+, or +:redis+.
|
41
|
+
def store=(value)
|
42
|
+
if value.is_a?(Symbol)
|
43
|
+
klass = STORES[value]
|
44
|
+
raise ArgumentError, "Unknown Gretel::Trails.store #{value.inspect}. Use any of #{STORES.inspect}." unless klass
|
45
|
+
self.store = klass
|
46
|
+
else
|
47
|
+
@store = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Uses the store to encode an array of links to a unique key that can be used in URLs.
|
52
|
+
def encode(links)
|
53
|
+
store.encode(links)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Uses the store to decode a unique key to an array of links.
|
57
|
+
def decode(key)
|
58
|
+
store.decode(key)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Deletes expired keys from the store.
|
62
|
+
# Not all stores support expiring keys, and will raise an exception if they don't.
|
63
|
+
def delete_expired
|
64
|
+
store.delete_expired
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the current number of trails in the store.
|
68
|
+
# Not all stores support counting keys, and will raise an exception if they don't.
|
69
|
+
def count
|
70
|
+
store.key_count
|
71
|
+
end
|
72
|
+
|
73
|
+
# Deletes all trails in the store.
|
74
|
+
# Not all stores support deleting trails, and will raise an exception if they don't.
|
75
|
+
def delete_all
|
76
|
+
store.delete_all_keys
|
77
|
+
end
|
78
|
+
|
79
|
+
# Name of trail param. Default: +:trail+.
|
80
|
+
def trail_param
|
81
|
+
@trail_param ||= :trail
|
82
|
+
end
|
83
|
+
|
84
|
+
# Sets the trail param.
|
85
|
+
attr_writer :trail_param
|
86
|
+
|
87
|
+
# Yields +self+ for configuration.
|
88
|
+
#
|
89
|
+
# Gretel::Trails.configure do |config|
|
90
|
+
# config.store = :db
|
91
|
+
# config.strategy = :hidden
|
92
|
+
# end
|
93
|
+
def configure
|
94
|
+
yield self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Resets all changes made to +Gretel::Trail+. Used for testing.
|
98
|
+
def reset!
|
99
|
+
instance_variables.each { |var| remove_instance_variable var }
|
100
|
+
STORES.each_value(&:reset!)
|
101
|
+
end
|
23
102
|
end
|
24
103
|
end
|
25
104
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Gretel::Renderer.class_eval do
|
2
|
+
# Loads parent links from trail if +params[:trail]+ is present.
|
3
|
+
def parent_links_for_with_trail(crumb)
|
4
|
+
if params[Gretel::Trails.trail_param].present?
|
5
|
+
Gretel::Trails.decode(params[Gretel::Trails.trail_param])
|
6
|
+
else
|
7
|
+
parent_links_for_without_trail(crumb)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
alias_method_chain :parent_links_for, :trail
|
12
|
+
|
13
|
+
# Returns encoded trail for the breadcrumb.
|
14
|
+
def trail
|
15
|
+
@trail ||= Gretel::Trails.encode(links)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Gretel
|
2
|
+
module Trails
|
3
|
+
class ActiveRecordStore < Store
|
4
|
+
class << self
|
5
|
+
# Number of seconds to keep the trails in the database.
|
6
|
+
# Default: +1.day+
|
7
|
+
def expires_in
|
8
|
+
@expires_in ||= 1.day
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets the number of seconds to keep the trails in the database.
|
12
|
+
attr_writer :expires_in
|
13
|
+
|
14
|
+
# Save array to database.
|
15
|
+
def save(array)
|
16
|
+
json = array.to_json
|
17
|
+
key = Digest::SHA1.hexdigest(json)
|
18
|
+
GretelTrail.set(key, array, expires_in.from_now)
|
19
|
+
key
|
20
|
+
end
|
21
|
+
|
22
|
+
# Retrieve array from database.
|
23
|
+
def retrieve(key)
|
24
|
+
GretelTrail.get(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Delete expired keys.
|
28
|
+
def delete_expired
|
29
|
+
GretelTrail.delete_expired
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets the number of trails stored in the database.
|
33
|
+
def key_count
|
34
|
+
GretelTrail.count
|
35
|
+
end
|
36
|
+
|
37
|
+
# Deletes all trails stored in the database.
|
38
|
+
def delete_all_keys
|
39
|
+
GretelTrail.delete_all
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class GretelTrail < ActiveRecord::Base
|
44
|
+
serialize :value, Array
|
45
|
+
|
46
|
+
def self.get(key)
|
47
|
+
find_by_key(key).try(:value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.set(key, value, expires_at)
|
51
|
+
find_or_initialize_by_key(key).tap do |rec|
|
52
|
+
rec.value = value
|
53
|
+
rec.expires_at = expires_at
|
54
|
+
rec.save
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.delete_expired
|
59
|
+
delete_all(["expires_at < ?", Time.now])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Gretel
|
2
|
+
module Trails
|
3
|
+
class RedisStore < Store
|
4
|
+
class << self
|
5
|
+
# Options to connect to Redis.
|
6
|
+
def connect_options
|
7
|
+
@connect_options ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sets the Redis connect options.
|
11
|
+
attr_writer :connect_options
|
12
|
+
|
13
|
+
# Number of seconds to keep the trails in Redis.
|
14
|
+
# Default: +1.day+
|
15
|
+
def expires_in
|
16
|
+
@expires_in ||= 1.day
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the number of seconds to keep the trails in Redis.
|
20
|
+
attr_writer :expires_in
|
21
|
+
|
22
|
+
# Save array to Redis.
|
23
|
+
def save(array)
|
24
|
+
json = array.to_json
|
25
|
+
key = Digest::SHA1.hexdigest(json)
|
26
|
+
redis.setex redis_key_for(key), expires_in, json
|
27
|
+
key
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieve array from Redis.
|
31
|
+
def retrieve(key)
|
32
|
+
if json = redis.get(redis_key_for(key))
|
33
|
+
JSON.parse(json)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reference to the Redis connection.
|
38
|
+
def redis
|
39
|
+
@redis ||= begin
|
40
|
+
raise "Redis needs to be installed in order for #{name} to use it. Please add `gem \"redis\"` to your Gemfile." unless defined?(Redis)
|
41
|
+
Redis.new(connect_options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Key to be stored in Redis.
|
48
|
+
def redis_key_for(key)
|
49
|
+
"gretel:trail:#{key}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Gretel
|
2
|
+
module Trails
|
3
|
+
class Store
|
4
|
+
class << self
|
5
|
+
# Save an encoded array to the store. It must return the trail key that
|
6
|
+
# can later be used to retrieve the array from the store.
|
7
|
+
def save(array)
|
8
|
+
raise "#{name} must implement #save to be able to save trails."
|
9
|
+
end
|
10
|
+
|
11
|
+
# Retrieve an encoded array from the store based on the saved key.
|
12
|
+
# It must return either the array, or nil if the key was not found.
|
13
|
+
def retrieve(key)
|
14
|
+
raise "#{name} must implement #retrieve to be able to retrieve trails."
|
15
|
+
end
|
16
|
+
|
17
|
+
# Deletes expired keys from the store.
|
18
|
+
def delete_expired
|
19
|
+
raise "#{name} doesn't support deleting expired keys."
|
20
|
+
end
|
21
|
+
|
22
|
+
# Gets the number of stored trail keys.
|
23
|
+
def key_count
|
24
|
+
raise "#{name} doesn't support counting trail keys."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Deletes all stored trail keys.
|
28
|
+
def delete_all_keys
|
29
|
+
raise "#{name} doesn't support deleting all trail keys."
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encode array of +links+ to unique trail key.
|
33
|
+
def encode(links)
|
34
|
+
arr = links.map { |link| [link.key, link.text, (link.text.html_safe? ? 1 : 0), link.url] }
|
35
|
+
save(arr)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Decode unique trail key to array of links.
|
39
|
+
def decode(key)
|
40
|
+
if arr = retrieve(key)
|
41
|
+
arr.map { |key, text, html_safe, url| Link.new(key.to_sym, (html_safe == 1 ? text.html_safe : text), url) }
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Resets all changes made to the store. Used for testing.
|
48
|
+
def reset!
|
49
|
+
instance_variables.each { |var| remove_instance_variable var }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Gretel
|
2
|
+
module Trails
|
3
|
+
class UrlStore < Store
|
4
|
+
class << self
|
5
|
+
# Secret used for crypting trail in URL that should be set to something
|
6
|
+
# unguessable. This is required when using trails, for the reason that
|
7
|
+
# unencrypted trails would be vulnerable to cross-site scripting attacks.
|
8
|
+
attr_accessor :secret
|
9
|
+
|
10
|
+
# Securely encodes encoded array to a trail string to be used in URL.
|
11
|
+
def save(array)
|
12
|
+
base64 = encode_base64(array)
|
13
|
+
hash = generate_hash(base64)
|
14
|
+
|
15
|
+
[hash, base64].join("_")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Securely decodes a URL trail string to encoded array.
|
19
|
+
def retrieve(key)
|
20
|
+
hash, base64 = key.split("_", 2)
|
21
|
+
|
22
|
+
if base64.blank?
|
23
|
+
Rails.logger.info "[Gretel] Trail decode failed: No Base64 in trail"
|
24
|
+
[]
|
25
|
+
elsif hash == generate_hash(base64)
|
26
|
+
decode_base64(base64)
|
27
|
+
else
|
28
|
+
Rails.logger.info "[Gretel] Trail decode failed: Invalid hash '#{hash}' in trail"
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Encodes links array to Base64, internally using JSON for serialization.
|
36
|
+
def encode_base64(array)
|
37
|
+
Base64.urlsafe_encode64(array.to_json)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Decodes links array from Base64.
|
41
|
+
def decode_base64(base64)
|
42
|
+
json = Base64.urlsafe_decode64(base64)
|
43
|
+
JSON.parse(json)
|
44
|
+
rescue
|
45
|
+
Rails.logger.info "[Gretel] Trail decode failed: Invalid Base64 '#{base64}' in trail"
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generates a salted hash of +base64+.
|
50
|
+
def generate_hash(base64)
|
51
|
+
raise "#{name}.secret is not set. Please set it to an unguessable string, e.g. from `rake secret`, or use `rails generate gretel:install` to generate and set it automatically." if secret.blank?
|
52
|
+
Digest::SHA1.hexdigest([base64, secret].join)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -29,11 +29,11 @@ end
|
|
29
29
|
|
30
30
|
Gretel::Renderer.class_eval do
|
31
31
|
# Moves the trail from the querystring into a data attribute.
|
32
|
-
def
|
33
|
-
if url.include?("#{Gretel.trail_param}=")
|
32
|
+
def breadcrumb_link_to_with_hidden_trail(name, url, options = {})
|
33
|
+
if url.include?("#{Gretel::Trails.trail_param}=")
|
34
34
|
uri = URI.parse(url)
|
35
35
|
query_hash = Hash[CGI.parse(uri.query.to_s).map { |k, v| [k, v.first] }]
|
36
|
-
trail = query_hash.delete(Gretel.trail_param.to_s)
|
36
|
+
trail = query_hash.delete(Gretel::Trails.trail_param.to_s)
|
37
37
|
|
38
38
|
options = options.dup
|
39
39
|
options[:data] ||= {}
|
@@ -42,16 +42,16 @@ Gretel::Renderer.class_eval do
|
|
42
42
|
uri.query = query_hash.to_query.presence
|
43
43
|
url = uri.to_s
|
44
44
|
end
|
45
|
-
|
45
|
+
breadcrumb_link_to_without_hidden_trail(name, url, options)
|
46
46
|
end
|
47
47
|
|
48
|
-
alias_method_chain :
|
48
|
+
alias_method_chain :breadcrumb_link_to, :hidden_trail
|
49
49
|
end
|
50
50
|
|
51
51
|
ActionView::Base.class_eval do
|
52
|
-
# View helper proxy to the breadcrumb renderer's
|
53
|
-
# removes trails from URLs and adds them as data attributes.
|
52
|
+
# View helper proxy to the breadcrumb renderer's breadcrumb_link_to that
|
53
|
+
# automatically removes trails from URLs and adds them as data attributes.
|
54
54
|
def breadcrumb_link_to(name, url, options = {})
|
55
|
-
gretel_renderer.
|
55
|
+
gretel_renderer.breadcrumb_link_to(name, url, options)
|
56
56
|
end unless method_defined?(:breadcrumb_link_to)
|
57
57
|
end
|
@@ -1,2 +1,2 @@
|
|
1
|
-
Gretel.
|
1
|
+
Gretel::Trails.store = :db
|
2
2
|
Gretel::Trails.strategy = :hidden
|
data/test/dummy/config/routes.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActiveRecordStoreTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
Gretel.reset!
|
6
|
+
Gretel::Trails.store = :db
|
7
|
+
Gretel::Trails.delete_all
|
8
|
+
|
9
|
+
@links = [
|
10
|
+
[:root, "Home", "/"],
|
11
|
+
[:store, "Store <b>Test</b>".html_safe, "/store"],
|
12
|
+
[:search, "Search", "/store/search?q=test"]
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
test "defaults" do
|
17
|
+
assert_equal 1.day, Gretel::Trails::ActiveRecordStore.expires_in
|
18
|
+
end
|
19
|
+
|
20
|
+
test "encoding" do
|
21
|
+
assert_equal "684c211441e72225cee89477a2d1f59e657c9e26",
|
22
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
23
|
+
end
|
24
|
+
|
25
|
+
test "decoding" do
|
26
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
27
|
+
decoded = Gretel::Trails.decode("684c211441e72225cee89477a2d1f59e657c9e26")
|
28
|
+
assert_equal @links, decoded.map { |link| [link.key, link.text, link.url] }
|
29
|
+
assert_equal [false, true, false], decoded.map { |link| link.text.html_safe? }
|
30
|
+
end
|
31
|
+
|
32
|
+
test "invalid trail" do
|
33
|
+
assert_equal [], Gretel::Trails.decode("asdgasdg")
|
34
|
+
end
|
35
|
+
|
36
|
+
test "delete expired" do
|
37
|
+
10.times { Gretel::Trails.encode([Gretel::Link.new(:test, SecureRandom.hex(20), "/test")]) }
|
38
|
+
assert_equal 10, Gretel::Trails.count
|
39
|
+
|
40
|
+
Gretel::Trails.delete_expired
|
41
|
+
assert_equal 10, Gretel::Trails.count
|
42
|
+
|
43
|
+
Timecop.travel(14.hours.from_now) do
|
44
|
+
5.times { Gretel::Trails.encode([Gretel::Link.new(:test, SecureRandom.hex(20), "/test")]) }
|
45
|
+
assert_equal 15, Gretel::Trails.count
|
46
|
+
end
|
47
|
+
|
48
|
+
Timecop.travel(25.hours.from_now) do
|
49
|
+
Gretel::Trails.delete_expired
|
50
|
+
assert_equal 5, Gretel::Trails.count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'fakeredis'
|
3
|
+
|
4
|
+
class RedisStoreTest < ActiveSupport::TestCase
|
5
|
+
setup do
|
6
|
+
Gretel.reset!
|
7
|
+
Gretel::Trails.store = :redis
|
8
|
+
|
9
|
+
@links = [
|
10
|
+
[:root, "Home", "/"],
|
11
|
+
[:store, "Store <b>Test</b>".html_safe, "/store"],
|
12
|
+
[:search, "Search", "/store/search?q=test"]
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
test "defaults" do
|
17
|
+
assert_equal 1.day, Gretel::Trails::RedisStore.expires_in
|
18
|
+
end
|
19
|
+
|
20
|
+
test "encoding" do
|
21
|
+
assert_equal "684c211441e72225cee89477a2d1f59e657c9e26",
|
22
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
23
|
+
end
|
24
|
+
|
25
|
+
test "decoding" do
|
26
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
27
|
+
decoded = Gretel::Trails.decode("684c211441e72225cee89477a2d1f59e657c9e26")
|
28
|
+
assert_equal @links, decoded.map { |link| [link.key, link.text, link.url] }
|
29
|
+
assert_equal [false, true, false], decoded.map { |link| link.text.html_safe? }
|
30
|
+
end
|
31
|
+
|
32
|
+
test "invalid trail" do
|
33
|
+
assert_equal [], Gretel::Trails.decode("asdgasdg")
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class UrlStoreTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
Gretel.reset!
|
6
|
+
|
7
|
+
Gretel::Trails.store = :url
|
8
|
+
Gretel::Trails::UrlStore.secret = "128107d341e912db791d98bbe874a8250f784b0a0b4dbc5d5032c0fc1ca7bda9c6ece667bd18d23736ee833ea79384176faeb54d2e0d21012898dde78631cdf1"
|
9
|
+
|
10
|
+
@links = [
|
11
|
+
[:root, "Home", "/"],
|
12
|
+
[:store, "Store <b>Test</b>".html_safe, "/store"],
|
13
|
+
[:search, "Search", "/store/search?q=test"]
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
test "encoding" do
|
18
|
+
assert_equal "5543214e6d7bbc3ba5209b2362cd7513d500f61b_W1sicm9vdCIsIkhvbWUiLDAsIi8iXSxbInN0b3JlIiwiU3RvcmUgPGI-VGVzdDwvYj4iLDEsIi9zdG9yZSJdLFsic2VhcmNoIiwiU2VhcmNoIiwwLCIvc3RvcmUvc2VhcmNoP3E9dGVzdCJdXQ==",
|
19
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
20
|
+
end
|
21
|
+
|
22
|
+
test "decoding" do
|
23
|
+
decoded = Gretel::Trails.decode("5543214e6d7bbc3ba5209b2362cd7513d500f61b_W1sicm9vdCIsIkhvbWUiLDAsIi8iXSxbInN0b3JlIiwiU3RvcmUgPGI-VGVzdDwvYj4iLDEsIi9zdG9yZSJdLFsic2VhcmNoIiwiU2VhcmNoIiwwLCIvc3RvcmUvc2VhcmNoP3E9dGVzdCJdXQ==")
|
24
|
+
assert_equal @links, decoded.map { |link| [link.key, link.text, link.url] }
|
25
|
+
assert_equal [false, true, false], decoded.map { |link| link.text.html_safe? }
|
26
|
+
end
|
27
|
+
|
28
|
+
test "invalid trail" do
|
29
|
+
assert_equal [], Gretel::Trails.decode("28f104524f5eaf6b3bd035710432fd2b9cbfd62c_X1sicm9vdCIsIkhvbWUiLDAsIi8iXSxbInN0b3JlIiwiU3RvcmUiLDAsIi9zdG9yZSJdLFsic2VhcmNoIiwiU2VhcmNoIiwwLCIvc3RvcmUvc2VhcmNoP3E9dGVzdCJdXQ==")
|
30
|
+
end
|
31
|
+
|
32
|
+
test "raises error if no secret set" do
|
33
|
+
Gretel::Trails::UrlStore.secret = nil
|
34
|
+
assert_raises RuntimeError do
|
35
|
+
Gretel::Trails.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/test/test_helper.rb
CHANGED
data/test/trails_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TrailsTest < ActiveSupport::TestCase
|
4
|
+
setup do
|
5
|
+
Gretel.reset!
|
6
|
+
end
|
7
|
+
|
8
|
+
test "defaults" do
|
9
|
+
assert_equal :trail, Gretel::Trails.trail_param
|
10
|
+
end
|
11
|
+
|
12
|
+
test "configuration block" do
|
13
|
+
Gretel::Trails.configure do |config|
|
14
|
+
config.trail_param = :set_from_config
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal :set_from_config, Gretel::Trails.trail_param
|
18
|
+
end
|
19
|
+
|
20
|
+
test "setting invalid store" do
|
21
|
+
assert_raises ArgumentError do
|
22
|
+
Gretel::Trails.store = :xx
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ViewHelpersTest < ActionView::TestCase
|
4
|
+
include Gretel::ViewHelpers
|
5
|
+
|
6
|
+
setup do
|
7
|
+
Gretel.reset!
|
8
|
+
Gretel::Trails::UrlStore.secret = "84f3196275c50b6fee3053c7b609b2633143f33f3536cb74abdf2753cca5a3e24b9dd93e4d7c75747c2f111821c7feb0e51e13485e4d772c17f60c1f8d832b72"
|
9
|
+
end
|
10
|
+
|
11
|
+
test "trail helper" do
|
12
|
+
breadcrumb :about
|
13
|
+
|
14
|
+
assert_equal "aec19c5388f02dd60151589ad01b4f3ec074598e_W1siYWJvdXQiLCJBYm91dCIsMCwiL2Fib3V0Il1d", breadcrumb_trail
|
15
|
+
end
|
16
|
+
|
17
|
+
test "loading trail" do
|
18
|
+
params[:trail] = "aec19c5388f02dd60151589ad01b4f3ec074598e_W1siYWJvdXQiLCJBYm91dCIsMCwiL2Fib3V0Il1d"
|
19
|
+
breadcrumb :recent_products
|
20
|
+
|
21
|
+
assert_equal %{<div class="breadcrumbs"><a href="/">Home</a> › <a href="/about">About</a> › <span class="current">Recent products</span></div>},
|
22
|
+
breadcrumbs
|
23
|
+
end
|
24
|
+
|
25
|
+
test "different trail param" do
|
26
|
+
Gretel::Trails.trail_param = :mytest
|
27
|
+
params[:mytest] = "aec19c5388f02dd60151589ad01b4f3ec074598e_W1siYWJvdXQiLCJBYm91dCIsMCwiL2Fib3V0Il1d"
|
28
|
+
breadcrumb :recent_products
|
29
|
+
|
30
|
+
assert_equal %{<div class="breadcrumbs"><a href="/">Home</a> › <a href="/about">About</a> › <span class="current">Recent products</span></div>},
|
31
|
+
breadcrumbs
|
32
|
+
end
|
33
|
+
|
34
|
+
test "unknown trail" do
|
35
|
+
params[:trail] = "notfound"
|
36
|
+
breadcrumb :recent_products
|
37
|
+
|
38
|
+
assert_equal %{<div class="breadcrumbs"><a href="/">Home</a> › <span class="current">Recent products</span></div>},
|
39
|
+
breadcrumbs
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gretel-trails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lasse Bunk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gretel
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.0.0.
|
19
|
+
version: 3.0.0.beta5
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.0.0.
|
26
|
+
version: 3.0.0.beta5
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - '>='
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: fakeredis
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.4.2
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: timecop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.6.3
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.6.3
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: capybara
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,10 +166,25 @@ files:
|
|
138
166
|
- gretel-trails.gemspec
|
139
167
|
- lib/assets/javascripts/gretel.trails.hidden.js.coffee.erb
|
140
168
|
- lib/assets/javascripts/gretel.trails.jsuri.js.coffee
|
169
|
+
- lib/generators/gretel/USAGE
|
170
|
+
- lib/generators/gretel/install_generator.rb
|
171
|
+
- lib/generators/gretel/templates/breadcrumbs.rb
|
172
|
+
- lib/generators/gretel/templates/trail_migration.rb
|
173
|
+
- lib/generators/gretel/trail/migration_generator.rb
|
141
174
|
- lib/gretel-trails.rb
|
142
175
|
- lib/gretel/trails.rb
|
143
176
|
- lib/gretel/trails/engine.rb
|
177
|
+
- lib/gretel/trails/patches.rb
|
178
|
+
- lib/gretel/trails/patches/gretel.rb
|
179
|
+
- lib/gretel/trails/patches/renderer.rb
|
180
|
+
- lib/gretel/trails/patches/view_helpers.rb
|
181
|
+
- lib/gretel/trails/stores.rb
|
182
|
+
- lib/gretel/trails/stores/active_record_store.rb
|
183
|
+
- lib/gretel/trails/stores/redis_store.rb
|
184
|
+
- lib/gretel/trails/stores/store.rb
|
185
|
+
- lib/gretel/trails/stores/url_store.rb
|
144
186
|
- lib/gretel/trails/strategies/hidden_strategy.rb
|
187
|
+
- lib/gretel/trails/tasks.rb
|
145
188
|
- lib/gretel/trails/version.rb
|
146
189
|
- test/dummy/README.rdoc
|
147
190
|
- test/dummy/Rakefile
|
@@ -193,7 +236,12 @@ files:
|
|
193
236
|
- test/dummy/test/fixtures/categories.yml
|
194
237
|
- test/dummy/test/fixtures/products.yml
|
195
238
|
- test/gretel_trails_test.rb
|
239
|
+
- test/stores/active_record_store_test.rb
|
240
|
+
- test/stores/redis_store_test.rb
|
241
|
+
- test/stores/url_store_test.rb
|
196
242
|
- test/test_helper.rb
|
243
|
+
- test/trails_test.rb
|
244
|
+
- test/view_helpers_test.rb
|
197
245
|
homepage: https://github.com/lassebunk/gretel-trails
|
198
246
|
licenses:
|
199
247
|
- MIT
|
@@ -269,4 +317,9 @@ test_files:
|
|
269
317
|
- test/dummy/test/fixtures/categories.yml
|
270
318
|
- test/dummy/test/fixtures/products.yml
|
271
319
|
- test/gretel_trails_test.rb
|
320
|
+
- test/stores/active_record_store_test.rb
|
321
|
+
- test/stores/redis_store_test.rb
|
322
|
+
- test/stores/url_store_test.rb
|
272
323
|
- test/test_helper.rb
|
324
|
+
- test/trails_test.rb
|
325
|
+
- test/view_helpers_test.rb
|