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