gretel 3.0.0.beta2 → 3.0.0.beta3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db6f1e42bb6b92c94ae0ea17349b87d352fda758
4
- data.tar.gz: 0b5532349fe0a6829bfd2e7d4c52aad04f7232a1
3
+ metadata.gz: a6dea6327b14a40f5f88c344548da90536778701
4
+ data.tar.gz: c527f98356d7ca6febb761ea239c030d3cfc6365
5
5
  SHA512:
6
- metadata.gz: a8975240e40fa018ba3a80ff04782657d3a13045ba293162655e0ddd8f3b33b2d6685e9695d4a8f46e7dd9e25cc169e183ee75412aae95d84282ea625c01a35c
7
- data.tar.gz: 105977c62283b95aa4c886ed38f85d2be8bb0ef64793c68673032bb416b9de284964de94d34a7cbe661d04c4a9f48bf7aeb3024c60c9e26f02ed84c2b59b1c4e
6
+ metadata.gz: e99745536c8bae608186322664689156e303f569961b1086278da7d66cf7ea2cdbcd84565eef961d6ab966d6044988057861656f7c6cec5b35e5a2905dfa53f6
7
+ data.tar.gz: e3eb531c6e4ac7e1a9e9c0d2e914f9b1321bdbb48f9aead24ff9a611fb3e5a4c93e6a26974c7fabd0529da3d6a89dbc7958af3f12449ad559c3b223f54610dc0
data/CHANGELOG.md CHANGED
@@ -10,6 +10,7 @@ Version 3.0
10
10
  * Breadcrumbs rendering is now done in a separate class to unclutter the view with helpers. The public API is still the same.
11
11
  * Support for rendering the breadcrumbs in different styles like ul- and ol lists, and for use with [Twitter Bootstrap](http://getbootstrap.com/). See the `:style` option in the readme for more info.
12
12
  * The `:show_root_alone` option is now called `:display_single_fragment` and can be used to display the breadcrumbs only when there are more than one link, also if it is not the root breadcrumb.
13
+ * Links yielded from `<%= breadcrumbs do |links| %>` now have a `current?` helper that returns true if the link is the last in the trail.
13
14
 
14
15
  Version 2.1
15
16
  -----------
data/README.md CHANGED
@@ -7,15 +7,15 @@ Gretel also supports [semantic breadcrumbs](http://support.google.com/webmasters
7
7
 
8
8
  Have fun! And please do write, if you (dis)like it – [lassebunk@gmail.com](mailto:lassebunk@gmail.com).
9
9
 
10
- New in version 3.0
11
- ------------------
10
+ New in version 3.0 :muscle:
11
+ ---------------------------
12
12
 
13
13
  * You can now set trails via the URL – `params[:trail]`. This makes it possible to link back to a different breadcrumb trail than the one specified in your breadcrumb,
14
- for example if you have a store with products that have a default parent to their category, but when linking from the reviews section, you want to link back to the reviews instead.
14
+ for example if you have a 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.
15
15
  Read more about trails below.
16
16
  * Breadcrumbs can now be rendered in different styles like ul- and ol lists, and for use with the [Twitter Bootstrap](http://getbootstrap.com/) framework. See the `:style` option below for more info.
17
17
  * Defining breadcrumbs using `Gretel::Crumbs.layout do ... end` in an initializer has been removed. See below for details on how to upgrade.
18
- * The `:show_root_alone` option is now called `:display_single_fragment` and can be used to hide the breadcrumbs when there are only one link, also if it is not the root breadcrumb.
18
+ * The `:show_root_alone` option is now called `:display_single_fragment` and can be used to hide the breadcrumbs when there is only one link, also if it is not the root breadcrumb.
19
19
  The old `:show_root_alone` option is still supported until Gretel version 4.0 and will show a deprecation warning when it's used.
20
20
 
21
21
  I hope you find these changes as useful as I did when I made them – if you have more suggestions, please create an [Issue](https://github.com/lassebunk/gretel/issues) or [Pull Request](https://github.com/lassebunk/gretel/pulls).
@@ -224,7 +224,7 @@ If you supply a block to the `breadcrumbs` method, it will yield an array with t
224
224
  <% if links.any? %>
225
225
  You are here:
226
226
  <% links.each do |link| %>
227
- <%= link_to link.text, link.url %> (<%= link.key %>)
227
+ <%= link_to link.text, link.url, class: (link.current? ? "current" : nil) %> (<%= link.key %>)
228
228
  <% end %>
229
229
  <% end %>
230
230
  <% end %>
@@ -235,7 +235,7 @@ Setting breadcrumb trails
235
235
 
236
236
  You can set a breadcrumb trail via `params[:trail]`. This makes it possible to link back to a different breadcrumb trail than the one specified in your breadcrumb.
237
237
 
238
- An example is if you have a store with products that have a default parent to their category, but when linking from the reviews section, you want to link back to the reviews instead.
238
+ An example is if you have a 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.
239
239
 
240
240
  ### Initial setup
241
241
 
@@ -252,7 +252,7 @@ This will create an initializer in *config/initializers/gretel.rb* that will con
252
252
  If you want to do it manually, you can put the following in *config/initializers/gretel.rb*:
253
253
 
254
254
  ```
255
- Gretel::Trail.secret = 'your_key_here' # Must be changed to something else to be secure
255
+ Gretel::Trail::UrlStore.secret = 'your_key_here' # Must be changed to something else to be secure
256
256
  ```
257
257
 
258
258
  You can generate a key using `SecureRandom.hex(64)`.
@@ -287,9 +287,9 @@ Please use the trail functionality with care; the trails can get very long.
287
287
  Nice to know
288
288
  ------------
289
289
 
290
- ### Access to view helpers
290
+ ### Access to view methods
291
291
 
292
- When configuring breadcrumbs, you have access to all view helpers of the view where the breadcrumbs are inserted.
292
+ When configuring breadcrumbs inside a `crumb :xx do ... end` block, you have access to all methods that are normally accessible in the view where the breadcrumbs are inserted. This includes your view helpers, `params`, `request`, etc.
293
293
 
294
294
  ### Using multiple breadcrumb configuration files
295
295
 
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'bundler/gem_tasks'
3
3
 
4
4
  Rake::TestTask.new do |t|
5
5
  t.libs << "test"
6
- t.test_files = FileList['test/*_test.rb']
6
+ t.test_files = FileList['test/**/*_test.rb']
7
7
  t.verbose = true
8
8
  end
9
9
 
data/gretel.gemspec CHANGED
@@ -17,6 +17,9 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^test/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
+ gem.add_dependency "rails", ">= 3.2.0"
20
21
  gem.add_development_dependency "rails", "~> 3.2.13"
21
22
  gem.add_development_dependency "sqlite3"
23
+ gem.add_development_dependency "fakeredis", "~> 0.4.2"
24
+ gem.add_development_dependency "timecop", "~> 0.6.3"
22
25
  end
@@ -12,7 +12,7 @@ module Gretel
12
12
  desc "Creates an initializer with trail secret"
13
13
  def create_initializer
14
14
  initializer "gretel.rb" do
15
- %{Gretel::Trail.secret = '#{SecureRandom.hex(64)}'}
15
+ %{Gretel::Trail::UrlStore.secret = '#{SecureRandom.hex(64)}'}
16
16
  end
17
17
  end
18
18
  end
@@ -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.rb CHANGED
@@ -19,6 +19,26 @@ module Gretel
19
19
  @breadcrumb_paths = paths
20
20
  end
21
21
 
22
+ # Param to contain trail. See +Gretel::Trail.trail_param+ for details.
23
+ def trail_param
24
+ Gretel::Trail.trail_param
25
+ end
26
+
27
+ # Sets the trail param. See +Gretel::Trail.trail_param+ for details.
28
+ def trail_param=(param)
29
+ Gretel::Trail.trail_param = param
30
+ end
31
+
32
+ # Trail store. See +Gretel::Trail.store+ for details.
33
+ def trail_store
34
+ Gretel::Trail.store
35
+ end
36
+
37
+ # Sets the trail store. See +Gretel::Trail.store+ for details.
38
+ def trail_store=(store)
39
+ Gretel::Trail.store = store
40
+ end
41
+
22
42
  # Whether to suppress deprecation warnings.
23
43
  def suppress_deprecation_warnings?
24
44
  !!@suppress_deprecation_warnings
@@ -45,6 +65,15 @@ module Gretel
45
65
  # Sets the Rails environment names with automatic configuration reload. Default is +["development"]+.
46
66
  attr_writer :reload_environments
47
67
 
68
+ # Yields this +Gretel+ to be configured.
69
+ #
70
+ # Gretel.configure do |config|
71
+ # config.trail_param = :other_param
72
+ # end
73
+ def configure
74
+ yield self
75
+ end
76
+
48
77
  # Resets all changes made to +Gretel+, +Gretel::Crumbs+, and +Gretel::Trail+. Used for testing.
49
78
  def reset!
50
79
  instance_variables.each { |var| remove_instance_variable var }
data/lib/gretel/link.rb CHANGED
@@ -5,5 +5,13 @@ module Gretel
5
5
  def initialize(key, text, url)
6
6
  @key, @text, @url = key, text, url
7
7
  end
8
+
9
+ def current!
10
+ @current = true
11
+ end
12
+
13
+ def current?
14
+ !!@current
15
+ end
8
16
  end
9
17
  end
@@ -117,6 +117,9 @@ module Gretel
117
117
  # Get trail
118
118
  links.unshift *trail_for(crumb)
119
119
 
120
+ # Set last link to current
121
+ links.last.try(:current!)
122
+
120
123
  links
121
124
  else
122
125
  []
@@ -169,10 +172,10 @@ module Gretel
169
172
  def render_semantic_fragment(fragment_tag, text, url, options = {})
170
173
  if fragment_tag
171
174
  text = content_tag(:span, text, itemprop: "title")
172
- text = link_to(text, url, itemprop: "url") if url.present?
175
+ text = render_link(text, url, itemprop: "url") if url.present?
173
176
  content_tag(fragment_tag, text, class: options[:class], itemscope: "", itemtype: "http://data-vocabulary.org/Breadcrumb")
174
177
  elsif url.present?
175
- content_tag(:div, link_to(content_tag(:span, text, itemprop: "title"), url, class: options[:class], itemprop: "url"), itemscope: "", itemtype: "http://data-vocabulary.org/Breadcrumb")
178
+ content_tag(:div, render_link(content_tag(:span, text, itemprop: "title"), url, class: options[:class], itemprop: "url"), itemscope: "", itemtype: "http://data-vocabulary.org/Breadcrumb")
176
179
  else
177
180
  content_tag(:div, content_tag(:span, text, class: options[:class], itemprop: "title"), itemscope: "", itemtype: "http://data-vocabulary.org/Breadcrumb")
178
181
  end
@@ -181,10 +184,10 @@ module Gretel
181
184
  # Renders regular, non-semantic fragment HTML.
182
185
  def render_nonsemantic_fragment(fragment_tag, text, url, options = {})
183
186
  if fragment_tag
184
- text = link_to(text, url) if url.present?
187
+ text = render_link(text, url) if url.present?
185
188
  content_tag(fragment_tag, text, class: options[:class])
186
189
  elsif url.present?
187
- link_to(text, url, class: options[:class])
190
+ render_link(text, url, class: options[:class])
188
191
  elsif options[:class].present?
189
192
  content_tag(:span, text, class: options[:class])
190
193
  else
@@ -192,6 +195,12 @@ module Gretel
192
195
  end
193
196
  end
194
197
 
198
+ # Renders a link. It is really just a proxy for +link_to+, but this can be
199
+ # used in plugins that want to change how links are rendered.
200
+ def render_link(name, url, options = {})
201
+ link_to(name, url, options)
202
+ end
203
+
195
204
  # Proxy to view context
196
205
  def method_missing(method, *args, &block)
197
206
  context.send(method, *args, &block)
data/lib/gretel/trail.rb CHANGED
@@ -1,32 +1,53 @@
1
+ require "gretel/trail/stores"
2
+ require "gretel/trail/tasks"
3
+
1
4
  module Gretel
2
5
  module Trail
6
+ STORES = {
7
+ url: UrlStore,
8
+ db: ActiveRecordStore,
9
+ redis: RedisStore
10
+ }
11
+
3
12
  class << self
4
- # Secret used for crypting trail in URL that should be set to something
5
- # unguessable. This is required when using trails, for the reason that
6
- # unencrypted trails would be vulnerable to cross-site scripting attacks.
7
- attr_accessor :secret
13
+ # Gets the store that is used to encode and decode trails.
14
+ # Default: +Gretel::Trail::UrlStore+
15
+ def store
16
+ @store ||= UrlStore
17
+ end
8
18
 
9
- # Securely encodes array of links to a trail string to be used in URL.
19
+ # Sets the store that is used to encode and decode trails.
20
+ # Can be a subclass of +Gretel::Trail::Store+, or a symbol: +:url+, +:db+, or +:redis+.
21
+ def store=(value)
22
+ if value.is_a?(Symbol)
23
+ klass = STORES[value]
24
+ raise ArgumentError, "Unknown Gretel::Trail.store #{value.inspect}. Use any of #{STORES.inspect}." unless klass
25
+ self.store = klass
26
+ else
27
+ @store = value
28
+ end
29
+ end
30
+
31
+ # Uses the store to encode an array of links to a unique key that can be used in URLs.
10
32
  def encode(links)
11
- base64 = encode_base64(links)
12
- hash = generate_hash(base64)
33
+ store.encode(links)
34
+ end
13
35
 
14
- [hash, base64].join("_")
36
+ # Uses the store to decode a unique key to an array of links.
37
+ def decode(key)
38
+ store.decode(key)
15
39
  end
16
40
 
17
- # Securely decodes a URL trail string to array of links.
18
- def decode(trail)
19
- hash, base64 = trail.split("_", 2)
41
+ # Deletes expired keys from the store.
42
+ # Not all stores support expiring keys, and will raise an exception if they don't.
43
+ def delete_expired
44
+ store.delete_expired
45
+ end
20
46
 
21
- if base64.blank?
22
- Rails.logger.info "[Gretel] Trail decode failed: No Base64 in trail"
23
- []
24
- elsif hash == generate_hash(base64)
25
- decode_base64(base64)
26
- else
27
- Rails.logger.info "[Gretel] Trail decode failed: Invalid hash '#{hash}' in trail"
28
- []
29
- end
47
+ # Returns the current number of trails in the store.
48
+ # Not all stores support counting keys, and will raise an exception if they don't.
49
+ def count
50
+ store.key_count
30
51
  end
31
52
 
32
53
  # Name of trail param. Default: +:trail+.
@@ -34,35 +55,13 @@ module Gretel
34
55
  @trail_param ||= :trail
35
56
  end
36
57
 
58
+ # Sets the trail param.
37
59
  attr_writer :trail_param
38
60
 
39
61
  # Resets all changes made to +Gretel::Trail+. Used for testing.
40
62
  def reset!
41
63
  instance_variables.each { |var| remove_instance_variable var }
42
- end
43
-
44
- private
45
-
46
- # Encodes links array to Base64, internally using JSON for serialization.
47
- def encode_base64(links)
48
- arr = links.map { |link| [link.key, link.text, (link.text.html_safe? ? 1 : 0), link.url] }
49
- Base64.urlsafe_encode64(arr.to_json)
50
- end
51
-
52
- # Decodes links array from Base64.
53
- def decode_base64(base64)
54
- json = Base64.urlsafe_decode64(base64)
55
- arr = JSON.parse(json)
56
- arr.map { |key, text, html_safe, url| Link.new(key.to_sym, (html_safe == 1 ? text.html_safe : text), url) }
57
- rescue
58
- Rails.logger.info "[Gretel] Trail decode failed: Invalid Base64 '#{base64}' in trail"
59
- []
60
- end
61
-
62
- # Generates a salted hash of +base64+.
63
- def generate_hash(base64)
64
- raise "Gretel::Trail.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?
65
- Digest::SHA1.hexdigest([base64, secret].join)
64
+ STORES.each_value(&:reset!)
66
65
  end
67
66
  end
68
67
  end
@@ -0,0 +1,4 @@
1
+ require "gretel/trail/stores/store"
2
+ require "gretel/trail/stores/url_store"
3
+ require "gretel/trail/stores/active_record_store"
4
+ require "gretel/trail/stores/redis_store"
@@ -0,0 +1,59 @@
1
+ module Gretel
2
+ module Trail
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
+ end
37
+
38
+ class GretelTrail < ActiveRecord::Base
39
+ serialize :value, Array
40
+
41
+ def self.get(key)
42
+ find_by_key(key).try(:value)
43
+ end
44
+
45
+ def self.set(key, value, expires_at)
46
+ find_or_initialize_by_key(key).tap do |rec|
47
+ rec.value = value
48
+ rec.expires_at = expires_at
49
+ rec.save
50
+ end
51
+ end
52
+
53
+ def self.delete_expired
54
+ delete_all(["expires_at < ?", Time.now])
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,54 @@
1
+ module Gretel
2
+ module Trail
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,49 @@
1
+ module Gretel
2
+ module Trail
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
+ # Encode array of +links+ to unique trail key.
28
+ def encode(links)
29
+ arr = links.map { |link| [link.key, link.text, (link.text.html_safe? ? 1 : 0), link.url] }
30
+ save(arr)
31
+ end
32
+
33
+ # Decode unique trail key to array of links.
34
+ def decode(key)
35
+ if arr = retrieve(key)
36
+ arr.map { |key, text, html_safe, url| Link.new(key.to_sym, (html_safe == 1 ? text.html_safe : text), url) }
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ # Resets all changes made to the store. Used for testing.
43
+ def reset!
44
+ instance_variables.each { |var| remove_instance_variable var }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ module Gretel
2
+ module Trail
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
@@ -0,0 +1,5 @@
1
+ require "rake"
2
+
3
+ Rake::Task.define_task("gretel:trails:delete_expired" => :environment) do
4
+ Gretel::Trail.delete_expired
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Gretel
2
- VERSION = "3.0.0.beta2"
2
+ VERSION = "3.0.0.beta3"
3
3
  end
@@ -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
@@ -11,19 +11,28 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20130122163051) do
14
+ ActiveRecord::Schema.define(:version => 20131015194052) do
15
15
 
16
- create_table "issues", force: true do |t|
16
+ create_table "gretel_trails", :force => true do |t|
17
+ t.string "key", :limit => 40
18
+ t.text "value"
19
+ t.datetime "expires_at"
20
+ end
21
+
22
+ add_index "gretel_trails", ["expires_at"], :name => "index_gretel_trails_on_expires_at"
23
+ add_index "gretel_trails", ["key"], :name => "index_gretel_trails_on_key", :unique => true
24
+
25
+ create_table "issues", :force => true do |t|
17
26
  t.string "title"
18
27
  t.integer "project_id"
19
- t.datetime "created_at", null: false
20
- t.datetime "updated_at", null: false
28
+ t.datetime "created_at", :null => false
29
+ t.datetime "updated_at", :null => false
21
30
  end
22
31
 
23
- create_table "projects", force: true do |t|
32
+ create_table "projects", :force => true do |t|
24
33
  t.string "name"
25
- t.datetime "created_at", null: false
26
- t.datetime "updated_at", null: false
34
+ t.datetime "created_at", :null => false
35
+ t.datetime "updated_at", :null => false
27
36
  end
28
37
 
29
38
  end
data/test/gretel_test.rb CHANGED
@@ -11,4 +11,12 @@ class GretelTest < ActiveSupport::TestCase
11
11
  assert_equal ["development"], Gretel.reload_environments
12
12
  assert !Gretel.suppress_deprecation_warnings?
13
13
  end
14
+
15
+ test "configuration block" do
16
+ Gretel.configure do |config|
17
+ config.trail_param = :other_param
18
+ end
19
+
20
+ assert_equal :other_param, Gretel.trail_param
21
+ end
14
22
  end
@@ -7,7 +7,7 @@ class HelperMethodsTest < ActionView::TestCase
7
7
 
8
8
  setup do
9
9
  Gretel.reset!
10
- Gretel::Trail.secret = "128107d341e912db791d98bbe874a8250f784b0a0b4dbc5d5032c0fc1ca7bda9c6ece667bd18d23736ee833ea79384176faeb54d2e0d21012898dde78631cdf1"
10
+ Gretel::Trail::UrlStore.secret = "128107d341e912db791d98bbe874a8250f784b0a0b4dbc5d5032c0fc1ca7bda9c6ece667bd18d23736ee833ea79384176faeb54d2e0d21012898dde78631cdf1"
11
11
  end
12
12
 
13
13
  # Breadcrumb generation
@@ -153,6 +153,16 @@ class HelperMethodsTest < ActionView::TestCase
153
153
  [:multiple_links_with_parent, "Contact form", "/about/contact/form"]], out
154
154
  end
155
155
 
156
+ test "sets current on last link in array" do
157
+ breadcrumb :multiple_links_with_parent
158
+
159
+ out = breadcrumbs do |links|
160
+ links.map(&:current?)
161
+ end
162
+
163
+ assert_equal [false, false, false, true], out
164
+ end
165
+
156
166
  test "without link" do
157
167
  breadcrumb :without_link
158
168
  assert_equal %{<div class="breadcrumbs"><a href="/">Home</a> &rsaquo; Also without link &rsaquo; <span class="current">Without link</span></div>},
data/test/test_helper.rb CHANGED
@@ -3,6 +3,7 @@ ENV["RAILS_ENV"] = "test"
3
3
 
4
4
  require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
5
  require "rails/test_help"
6
+ require "timecop"
6
7
 
7
8
  Rails.backtrace_cleaner.remove_silencers!
8
9
 
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveRecordStoreTest < ActiveSupport::TestCase
4
+ setup do
5
+ Gretel.reset!
6
+ Gretel::Trail.store = :db
7
+
8
+ @links = [
9
+ [:root, "Home", "/"],
10
+ [:store, "Store <b>Test</b>".html_safe, "/store"],
11
+ [:search, "Search", "/store/search?q=test"]
12
+ ]
13
+ end
14
+
15
+ test "defaults" do
16
+ assert_equal 1.day, Gretel::Trail::ActiveRecordStore.expires_in
17
+ end
18
+
19
+ test "encoding" do
20
+ assert_equal "684c211441e72225cee89477a2d1f59e657c9e26",
21
+ Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
22
+ end
23
+
24
+ test "decoding" do
25
+ Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
26
+ decoded = Gretel::Trail.decode("684c211441e72225cee89477a2d1f59e657c9e26")
27
+ assert_equal @links, decoded.map { |link| [link.key, link.text, link.url] }
28
+ assert_equal [false, true, false], decoded.map { |link| link.text.html_safe? }
29
+ end
30
+
31
+ test "invalid trail" do
32
+ assert_equal [], Gretel::Trail.decode("asdgasdg")
33
+ end
34
+
35
+ test "delete expired" do
36
+ 10.times { Gretel::Trail.encode([Gretel::Link.new(:test, SecureRandom.hex(20), "/test")]) }
37
+ assert_equal 10, Gretel::Trail.count
38
+
39
+ Gretel::Trail.delete_expired
40
+ assert_equal 10, Gretel::Trail.count
41
+
42
+ Timecop.travel(14.hours.from_now) do
43
+ 5.times { Gretel::Trail.encode([Gretel::Link.new(:test, SecureRandom.hex(20), "/test")]) }
44
+ assert_equal 15, Gretel::Trail.count
45
+ end
46
+
47
+ Timecop.travel(25.hours.from_now) do
48
+ Gretel::Trail.delete_expired
49
+ assert_equal 5, Gretel::Trail.count
50
+ end
51
+ end
52
+ 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::Trail.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::Trail::RedisStore.expires_in
18
+ end
19
+
20
+ test "encoding" do
21
+ assert_equal "684c211441e72225cee89477a2d1f59e657c9e26",
22
+ Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
23
+ end
24
+
25
+ test "decoding" do
26
+ Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
27
+ decoded = Gretel::Trail.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::Trail.decode("asdgasdg")
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class TrailTest < ActiveSupport::TestCase
4
+ setup do
5
+ Gretel.reset!
6
+ end
7
+
8
+ test "defaults" do
9
+ assert_equal :trail, Gretel::Trail.trail_param
10
+ end
11
+
12
+ test "setting invalid store" do
13
+ assert_raises ArgumentError do
14
+ Gretel::Trail.store = :xx
15
+ end
16
+ end
17
+
18
+ test "setting store options on main module" do
19
+ assert_equal :trail, Gretel.trail_param
20
+ Gretel.trail_param = :other_param
21
+ assert_equal :other_param, Gretel::Trail.trail_param
22
+
23
+ assert_equal Gretel::Trail::UrlStore, Gretel.trail_store
24
+ Gretel.trail_store = :redis
25
+ assert_equal Gretel::Trail::RedisStore, Gretel::Trail.store
26
+ end
27
+ end
@@ -1,8 +1,12 @@
1
1
  require 'test_helper'
2
2
 
3
- class TrailTest < ActiveSupport::TestCase
3
+ class UrlStoreTest < ActiveSupport::TestCase
4
4
  setup do
5
- Gretel::Trail.secret = "128107d341e912db791d98bbe874a8250f784b0a0b4dbc5d5032c0fc1ca7bda9c6ece667bd18d23736ee833ea79384176faeb54d2e0d21012898dde78631cdf1"
5
+ Gretel.reset!
6
+
7
+ Gretel::Trail.store = :url
8
+ Gretel::Trail::UrlStore.secret = "128107d341e912db791d98bbe874a8250f784b0a0b4dbc5d5032c0fc1ca7bda9c6ece667bd18d23736ee833ea79384176faeb54d2e0d21012898dde78631cdf1"
9
+
6
10
  @links = [
7
11
  [:root, "Home", "/"],
8
12
  [:store, "Store <b>Test</b>".html_safe, "/store"],
@@ -10,10 +14,6 @@ class TrailTest < ActiveSupport::TestCase
10
14
  ]
11
15
  end
12
16
 
13
- test "defaults" do
14
- assert_equal :trail, Gretel::Trail.trail_param
15
- end
16
-
17
17
  test "encoding" do
18
18
  assert_equal "5543214e6d7bbc3ba5209b2362cd7513d500f61b_W1sicm9vdCIsIkhvbWUiLDAsIi8iXSxbInN0b3JlIiwiU3RvcmUgPGI-VGVzdDwvYj4iLDEsIi9zdG9yZSJdLFsic2VhcmNoIiwiU2VhcmNoIiwwLCIvc3RvcmUvc2VhcmNoP3E9dGVzdCJdXQ==",
19
19
  Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
@@ -28,4 +28,11 @@ class TrailTest < ActiveSupport::TestCase
28
28
  test "invalid trail" do
29
29
  assert_equal [], Gretel::Trail.decode("28f104524f5eaf6b3bd035710432fd2b9cbfd62c_X1sicm9vdCIsIkhvbWUiLDAsIi8iXSxbInN0b3JlIiwiU3RvcmUiLDAsIi9zdG9yZSJdLFsic2VhcmNoIiwiU2VhcmNoIiwwLCIvc3RvcmUvc2VhcmNoP3E9dGVzdCJdXQ==")
30
30
  end
31
+
32
+ test "raises error if no secret set" do
33
+ Gretel::Trail::UrlStore.secret = nil
34
+ assert_raises RuntimeError do
35
+ Gretel::Trail.encode(@links.map { |key, text, url| Gretel::Link.new(key, text, url) })
36
+ end
37
+ end
31
38
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gretel
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta2
4
+ version: 3.0.0.beta3
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-12 00:00:00.000000000 Z
11
+ date: 2013-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rails
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,34 @@ dependencies:
38
52
  - - '>='
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakeredis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.4.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.4.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.3
41
83
  description: Gretel is a Ruby on Rails plugin that makes it easy yet flexible to create
42
84
  breadcrumbs.
43
85
  email:
@@ -57,6 +99,8 @@ files:
57
99
  - lib/generators/gretel/USAGE
58
100
  - lib/generators/gretel/install_generator.rb
59
101
  - lib/generators/gretel/templates/breadcrumbs.rb
102
+ - lib/generators/gretel/templates/trail_migration.rb
103
+ - lib/generators/gretel/trail/migration_generator.rb
60
104
  - lib/gretel.rb
61
105
  - lib/gretel/crumb.rb
62
106
  - lib/gretel/crumbs.rb
@@ -64,6 +108,12 @@ files:
64
108
  - lib/gretel/link.rb
65
109
  - lib/gretel/renderer.rb
66
110
  - lib/gretel/trail.rb
111
+ - lib/gretel/trail/stores.rb
112
+ - lib/gretel/trail/stores/active_record_store.rb
113
+ - lib/gretel/trail/stores/redis_store.rb
114
+ - lib/gretel/trail/stores/store.rb
115
+ - lib/gretel/trail/stores/url_store.rb
116
+ - lib/gretel/trail/tasks.rb
67
117
  - lib/gretel/version.rb
68
118
  - lib/gretel/view_helpers.rb
69
119
  - test/deprecated_test.rb
@@ -96,6 +146,7 @@ files:
96
146
  - test/dummy/config/routes.rb
97
147
  - test/dummy/db/migrate/20130122163007_create_projects.rb
98
148
  - test/dummy/db/migrate/20130122163051_create_issues.rb
149
+ - test/dummy/db/migrate/20131015194052_create_gretel_trails.rb
99
150
  - test/dummy/db/schema.rb
100
151
  - test/dummy/lib/assets/.gitkeep
101
152
  - test/dummy/log/.gitkeep
@@ -109,7 +160,10 @@ files:
109
160
  - test/gretel_test.rb
110
161
  - test/helper_methods_test.rb
111
162
  - test/test_helper.rb
112
- - test/trail_test.rb
163
+ - test/trails/active_record_store_test.rb
164
+ - test/trails/redis_store_test.rb
165
+ - test/trails/trail_test.rb
166
+ - test/trails/url_store_test.rb
113
167
  homepage: http://github.com/lassebunk/gretel
114
168
  licenses:
115
169
  - MIT
@@ -165,6 +219,7 @@ test_files:
165
219
  - test/dummy/config/routes.rb
166
220
  - test/dummy/db/migrate/20130122163007_create_projects.rb
167
221
  - test/dummy/db/migrate/20130122163051_create_issues.rb
222
+ - test/dummy/db/migrate/20131015194052_create_gretel_trails.rb
168
223
  - test/dummy/db/schema.rb
169
224
  - test/dummy/lib/assets/.gitkeep
170
225
  - test/dummy/log/.gitkeep
@@ -178,4 +233,7 @@ test_files:
178
233
  - test/gretel_test.rb
179
234
  - test/helper_methods_test.rb
180
235
  - test/test_helper.rb
181
- - test/trail_test.rb
236
+ - test/trails/active_record_store_test.rb
237
+ - test/trails/redis_store_test.rb
238
+ - test/trails/trail_test.rb
239
+ - test/trails/url_store_test.rb