friendly_id 4.0.1 → 4.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.
- data/Changelog.md +6 -0
- data/Guide.rdoc +32 -4
- data/lib/friendly_id.rb +1 -1
- data/lib/friendly_id/history.rb +8 -6
- data/lib/friendly_id/reserved.rb +18 -0
- data/lib/friendly_id/slug_generator.rb +9 -2
- data/lib/friendly_id/slugged.rb +15 -4
- data/test/history_test.rb +10 -0
- data/test/slugged_test.rb +17 -0
- metadata +20 -20
data/Changelog.md
CHANGED
@@ -6,6 +6,12 @@ suggestions, ideas and improvements to FriendlyId.
|
|
6
6
|
* Table of Contents
|
7
7
|
{:toc}
|
8
8
|
|
9
|
+
## 4.0.2 (2012-03-12)
|
10
|
+
|
11
|
+
* Improved conflict handling and performance in History module (Erik Ogan and Thomas Shafer)
|
12
|
+
* Fixed bug that impeded using underscores as a sequence separator (Erik Ogan and Thomas Shafer)
|
13
|
+
* Minor documentation improvements (Norman Clarke)
|
14
|
+
|
9
15
|
## 4.0.1 (2012-02-29)
|
10
16
|
|
11
17
|
* Added support for Globalize 3 (Enrico Pilotto and Philip Arndt)
|
data/Guide.rdoc
CHANGED
@@ -241,6 +241,21 @@ This functionality was in fact taken from earlier versions of FriendlyId.
|
|
241
241
|
|
242
242
|
==== Gotchas: Common Problems
|
243
243
|
|
244
|
+
===== Slugs That Begin With Numbers
|
245
|
+
|
246
|
+
Ruby's `to_i` function casts strings to integers in such a way that +23abc.to_i+
|
247
|
+
returns 23. Because FriendlyId falls back to finding by numeric id, this means
|
248
|
+
that if you attempt to find a record with a non-existant slug, and that slug
|
249
|
+
begins with a number, your find will probably return the wrong record.
|
250
|
+
|
251
|
+
There are two fairly simple ways to avoid this:
|
252
|
+
|
253
|
+
* Use validations to ensure that slugs don't begin with numbers.
|
254
|
+
* Use explicit finders like +find_by_id+ to always find by the numeric id, or
|
255
|
+
+find_by_slug+ to always find using the friendly id.
|
256
|
+
|
257
|
+
===== Concurrency Issues
|
258
|
+
|
244
259
|
FriendlyId uses a before_validation callback to generate and set the slug. This
|
245
260
|
means that if you create two model instances before saving them, it's possible
|
246
261
|
they will generate the same slug, and the second save will fail.
|
@@ -249,14 +264,10 @@ This can happen in two fairly normal cases: the first, when a model using nested
|
|
249
264
|
attributes creates more than one record for a model that uses friendly_id. The
|
250
265
|
second, in concurrent code, either in threads or multiple processes.
|
251
266
|
|
252
|
-
===== Nested Attributes
|
253
|
-
|
254
267
|
To solve the nested attributes issue, I recommend simply avoiding them when
|
255
268
|
creating more than one nested record for a model that uses FriendlyId. See {this
|
256
269
|
Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
|
257
270
|
|
258
|
-
===== Concurrency
|
259
|
-
|
260
271
|
To solve the concurrency issue, I recommend locking the model's table against
|
261
272
|
inserts while when saving the record. See {this Github
|
262
273
|
issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
@@ -464,4 +475,21 @@ FriendlyId.defaults}:
|
|
464
475
|
config.use :reserved
|
465
476
|
# Reserve words for English and Spanish URLs
|
466
477
|
config.reserved_words = %w(new edit nueva nuevo editar)
|
478
|
+
end
|
479
|
+
|
480
|
+
Note that the error message will appear on the field +:friendly_id+. If you are
|
481
|
+
using Rails's scaffolded form errors display, then it will have no field to
|
482
|
+
highlight. If you'd like to change this so that scaffolding works as expected,
|
483
|
+
one way to accomplish this is to move the error message to a different field.
|
484
|
+
For example:
|
485
|
+
|
486
|
+
class Person < ActiveRecord::Base
|
487
|
+
extend FriendlyId
|
488
|
+
friendly_id :name, use: :slugged
|
489
|
+
|
490
|
+
after_validation :move_friendly_id_error_to_name
|
491
|
+
|
492
|
+
def move_friendly_id_error_to_name
|
493
|
+
errors.messages[:name] = errors.messages.delete(:friendly_id)
|
494
|
+
end
|
467
495
|
end
|
data/lib/friendly_id.rb
CHANGED
data/lib/friendly_id/history.rb
CHANGED
@@ -117,18 +117,20 @@ method.
|
|
117
117
|
module SlugGenerator
|
118
118
|
|
119
119
|
private
|
120
|
+
def last_in_sequence
|
121
|
+
@_last_in_sequence ||= extract_sequence_from_slug(conflict.slug)
|
122
|
+
end
|
120
123
|
|
121
124
|
def conflicts
|
122
125
|
sluggable_class = friendly_id_config.model_class
|
123
126
|
pkey = sluggable_class.primary_key
|
124
127
|
value = sluggable.send pkey
|
125
|
-
# TODO this is a bit of a performance drain right now; optimize before next release.
|
126
|
-
scope = sluggable_class.unscoped.includes(:slugs).where("#{Slug.quoted_table_name}.slug = ? OR #{Slug.quoted_table_name}.slug LIKE ?", normalized, wildcard)
|
127
|
-
scope = scope.where(Slug.table_name => {:sluggable_type => sluggable_class.name})
|
128
|
-
scope = scope.where("#{sluggable_class.table_name}.#{pkey} <> ?", value) unless sluggable.new_record?
|
129
|
-
scope.order("LENGTH(#{Slug.quoted_table_name}.slug) DESC, #{Slug.quoted_table_name}.slug DESC")
|
130
|
-
end
|
131
128
|
|
129
|
+
scope = Slug.where("slug = ? OR slug LIKE ?", normalized, wildcard)
|
130
|
+
scope = scope.where(:sluggable_type => sluggable_class.name)
|
131
|
+
scope = scope.where("sluggable_id <> ?", value) unless sluggable.new_record?
|
132
|
+
scope.order("LENGTH(slug) DESC, slug DESC")
|
133
|
+
end
|
132
134
|
end
|
133
135
|
end
|
134
136
|
end
|
data/lib/friendly_id/reserved.rb
CHANGED
@@ -16,6 +16,24 @@ FriendlyId.defaults}:
|
|
16
16
|
# Reserve words for English and Spanish URLs
|
17
17
|
config.reserved_words = %w(new edit nueva nuevo editar)
|
18
18
|
end
|
19
|
+
|
20
|
+
Note that the error message will appear on the field +:friendly_id+. If you are
|
21
|
+
using Rails's scaffolded form errors display, then it will have no field to
|
22
|
+
highlight. If you'd like to change this so that scaffolding works as expected,
|
23
|
+
one way to accomplish this is to move the error message to a different field.
|
24
|
+
For example:
|
25
|
+
|
26
|
+
class Person < ActiveRecord::Base
|
27
|
+
extend FriendlyId
|
28
|
+
friendly_id :name, use: :slugged
|
29
|
+
|
30
|
+
after_validation :move_friendly_id_error_to_name
|
31
|
+
|
32
|
+
def move_friendly_id_error_to_name
|
33
|
+
errors.messages[:name] = errors.messages.delete(:friendly_id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
19
37
|
=end
|
20
38
|
module Reserved
|
21
39
|
|
@@ -27,8 +27,12 @@ module FriendlyId
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def last_in_sequence
|
30
|
+
@_last_in_sequence ||= extract_sequence_from_slug(conflict.to_param)
|
31
|
+
end
|
32
|
+
|
33
|
+
def extract_sequence_from_slug(slug)
|
30
34
|
# Don't assume that the separator is unique in the slug.
|
31
|
-
|
35
|
+
slug.gsub(/^#{Regexp.quote(normalized)}(#{Regexp.quote(separator)})?/, '').to_i
|
32
36
|
end
|
33
37
|
|
34
38
|
def column
|
@@ -65,7 +69,10 @@ module FriendlyId
|
|
65
69
|
end
|
66
70
|
|
67
71
|
def wildcard
|
68
|
-
|
72
|
+
# Underscores (matching a single character) and percent signs (matching
|
73
|
+
# any number of characters) need to be escaped
|
74
|
+
# (While this seems like an excessive number of backslashes, it is correct)
|
75
|
+
"#{normalized}#{separator}".gsub(/[_%]/, '\\\\\&') + '%'
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
data/lib/friendly_id/slugged.rb
CHANGED
@@ -160,6 +160,21 @@ This functionality was in fact taken from earlier versions of FriendlyId.
|
|
160
160
|
|
161
161
|
==== Gotchas: Common Problems
|
162
162
|
|
163
|
+
===== Slugs That Begin With Numbers
|
164
|
+
|
165
|
+
Ruby's `to_i` function casts strings to integers in such a way that +23abc.to_i+
|
166
|
+
returns 23. Because FriendlyId falls back to finding by numeric id, this means
|
167
|
+
that if you attempt to find a record with a non-existant slug, and that slug
|
168
|
+
begins with a number, your find will probably return the wrong record.
|
169
|
+
|
170
|
+
There are two fairly simple ways to avoid this:
|
171
|
+
|
172
|
+
* Use validations to ensure that slugs don't begin with numbers.
|
173
|
+
* Use explicit finders like +find_by_id+ to always find by the numeric id, or
|
174
|
+
+find_by_slug+ to always find using the friendly id.
|
175
|
+
|
176
|
+
===== Concurrency Issues
|
177
|
+
|
163
178
|
FriendlyId uses a before_validation callback to generate and set the slug. This
|
164
179
|
means that if you create two model instances before saving them, it's possible
|
165
180
|
they will generate the same slug, and the second save will fail.
|
@@ -168,14 +183,10 @@ This can happen in two fairly normal cases: the first, when a model using nested
|
|
168
183
|
attributes creates more than one record for a model that uses friendly_id. The
|
169
184
|
second, in concurrent code, either in threads or multiple processes.
|
170
185
|
|
171
|
-
===== Nested Attributes
|
172
|
-
|
173
186
|
To solve the nested attributes issue, I recommend simply avoiding them when
|
174
187
|
creating more than one nested record for a model that uses FriendlyId. See {this
|
175
188
|
Github issue}[https://github.com/norman/friendly_id/issues/185] for discussion.
|
176
189
|
|
177
|
-
===== Concurrency
|
178
|
-
|
179
190
|
To solve the concurrency issue, I recommend locking the model's table against
|
180
191
|
inserts while when saving the record. See {this Github
|
181
192
|
issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
data/test/history_test.rb
CHANGED
@@ -68,6 +68,16 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
test "should create correct sequence numbers even when some conflicted slugs have changed" do
|
72
|
+
transaction do
|
73
|
+
record1 = model_class.create! :name => 'hello'
|
74
|
+
record2 = model_class.create! :name => 'hello!'
|
75
|
+
record2.update_attributes :name => 'goodbye'
|
76
|
+
record3 = model_class.create! :name => 'hello!'
|
77
|
+
assert_equal 'hello--3', record3.slug
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
71
81
|
|
72
82
|
test "should raise error if used with scoped" do
|
73
83
|
model_class = Class.new(ActiveRecord::Base) do
|
data/test/slugged_test.rb
CHANGED
@@ -10,6 +10,15 @@ class Article < ActiveRecord::Base
|
|
10
10
|
friendly_id :name, :use => :slugged
|
11
11
|
end
|
12
12
|
|
13
|
+
class Novelist < ActiveRecord::Base
|
14
|
+
extend FriendlyId
|
15
|
+
friendly_id :name, :use => :slugged, :sequence_separator => '_'
|
16
|
+
|
17
|
+
def normalize_friendly_id(string)
|
18
|
+
super.gsub("-", "_")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
class SluggedTest < MiniTest::Unit::TestCase
|
14
23
|
|
15
24
|
include FriendlyId::Test
|
@@ -112,6 +121,14 @@ class SlugGeneratorTest < MiniTest::Unit::TestCase
|
|
112
121
|
end
|
113
122
|
end
|
114
123
|
|
124
|
+
test "should correctly sequence slugs with underscores" do
|
125
|
+
transaction do
|
126
|
+
record1 = Novelist.create! :name => 'wordsfail, buildings tumble'
|
127
|
+
record2 = Novelist.create! :name => 'word fail'
|
128
|
+
assert_equal 'word_fail', record2.slug
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
115
132
|
end
|
116
133
|
|
117
134
|
class SlugSeparatorTest < MiniTest::Unit::TestCase
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.
|
4
|
+
version: 4.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-03-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: railties
|
16
|
-
requirement: &
|
16
|
+
requirement: &70322875898160 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.2.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70322875898160
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activerecord
|
27
|
-
requirement: &
|
27
|
+
requirement: &70322875893740 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.2.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70322875893740
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: minitest
|
38
|
-
requirement: &
|
38
|
+
requirement: &70322875889580 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70322875889580
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mocha
|
49
|
-
requirement: &
|
49
|
+
requirement: &70322875886660 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70322875886660
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: maruku
|
60
|
-
requirement: &
|
60
|
+
requirement: &70322875883700 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70322875883700
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: yard
|
71
|
-
requirement: &
|
71
|
+
requirement: &70322875857480 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70322875857480
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: i18n
|
82
|
-
requirement: &
|
82
|
+
requirement: &70322875830720 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70322875830720
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: ffaker
|
93
|
-
requirement: &
|
93
|
+
requirement: &70322875828040 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70322875828040
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: simplecov
|
104
|
-
requirement: &
|
104
|
+
requirement: &70322875809660 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70322875809660
|
113
113
|
description: ! 'FriendlyId is the "Swiss Army bulldozer" of slugging and permalink
|
114
114
|
plugins for
|
115
115
|
|