nateabbott-friendly-id 2.2.1

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/test/schema.rb ADDED
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ ActiveRecord::Schema.define(:version => 1) do
4
+
5
+ create_table "books", :force => true do |t|
6
+ t.column "title", "string"
7
+ t.column "type", "text"
8
+ end
9
+
10
+ create_table "things", :force => true do |t|
11
+ t.column "name", "string"
12
+ end
13
+
14
+ create_table "posts", :force => true do |t|
15
+ t.column "title", "string"
16
+ t.column "content", "text"
17
+ t.column "published", "boolean", :default => false
18
+ t.column "created_at", "datetime"
19
+ t.column "updated_at", "datetime"
20
+ end
21
+
22
+ create_table "users", :force => true do |t|
23
+ t.column "login", "string"
24
+ t.column "email", "string"
25
+ t.column "created_at", "datetime"
26
+ t.column "updated_at", "datetime"
27
+ end
28
+
29
+ create_table "people", :force => true do |t|
30
+ t.column "name", "string"
31
+ t.column "country_id", "integer"
32
+ end
33
+
34
+ create_table "countries", :force => true do |t|
35
+ t.column "name", "string"
36
+ end
37
+
38
+ create_table "events", :force => true do |t|
39
+ t.column "name", "string"
40
+ t.column "event_date", "datetime"
41
+ end
42
+
43
+ create_table "cities", :force => true do |t|
44
+ t.column "name", "string"
45
+ t.column "population", "integer"
46
+ t.column "my_slug", "string"
47
+ end
48
+
49
+ create_table "districts", :force => true do |t|
50
+ t.column "name", "string"
51
+ t.column "cached_slug", "string"
52
+ end
53
+
54
+ create_table "slugs", :force => true do |t|
55
+ t.column "name", "string"
56
+ t.column "sluggable_id", "integer"
57
+ t.column "sequence", "integer", :null => false, :default => 1
58
+ t.column "sluggable_type", "string", :limit => 40
59
+ t.column "scope", "string", :limit => 40
60
+ t.column "created_at", "datetime"
61
+ end
62
+
63
+ add_index "slugs", ["sluggable_id"], :name => "index_slugs_on_sluggable_id"
64
+ add_index "slugs", ["name", "sluggable_type", "scope", "sequence"], :name => "index_slugs_on_n_s_s_and_s", :unique => true
65
+
66
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class ScopedModelTest < Test::Unit::TestCase
6
+
7
+ context "A slugged model that uses a scope" do
8
+
9
+ setup do
10
+ Person.delete_all
11
+ Country.delete_all
12
+ Slug.delete_all
13
+ @usa = Country.create!(:name => "USA")
14
+ @canada = Country.create!(:name => "Canada")
15
+ @person = Person.create!(:name => "John Smith", :country => @usa)
16
+ @person2 = Person.create!(:name => "John Smith", :country => @canada)
17
+ end
18
+
19
+ should "find all scoped records without scope" do
20
+ assert_equal 2, Person.find(:all, @person.friendly_id).size
21
+ end
22
+
23
+ should "find a single scoped records with a scope" do
24
+ assert Person.find(@person.friendly_id, :scope => @person.country.to_param)
25
+ end
26
+
27
+ should "raise an error when finding a single scoped record with no scope" do
28
+ assert_raises ActiveRecord::RecordNotFound do
29
+ Person.find(@person.friendly_id)
30
+ end
31
+ end
32
+
33
+ should "append scope error info when missing scope causes a find to fail" do
34
+ begin
35
+ Person.find(@person.friendly_id)
36
+ fail "The find should not have succeeded"
37
+ rescue ActiveRecord::RecordNotFound => e
38
+ assert_match /expected scope/, e.message
39
+ end
40
+ end
41
+
42
+ should "append scope error info when the scope value causes a find to fail" do
43
+ begin
44
+ Person.find(@person.friendly_id, :scope => "badscope")
45
+ fail "The find should not have succeeded"
46
+ rescue ActiveRecord::RecordNotFound => e
47
+ assert_match /scope=badscope/, e.message
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
data/test/slug_test.rb ADDED
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class SlugTest < Test::Unit::TestCase
6
+
7
+ context "a slug" do
8
+
9
+ setup do
10
+ Slug.delete_all
11
+ Post.delete_all
12
+ end
13
+
14
+ should "indicate if it is the most recent slug" do
15
+ @post = Post.create!(:title => "test title", :content => "test content")
16
+ @post.title = "a new title"
17
+ @post.save!
18
+ assert @post.slugs.last.is_most_recent?
19
+ assert !@post.slugs.first.is_most_recent?
20
+ end
21
+
22
+ end
23
+
24
+ context "the Slug class" do
25
+
26
+ should "parse the slug name and sequence" do
27
+ assert_equal ["test", "2"], Slug::parse("test--2")
28
+ end
29
+
30
+ should "parse with a default sequence of 1" do
31
+ assert_equal ["test", "1"], Slug::parse("test")
32
+ end
33
+
34
+ should "should strip diacritics" do
35
+ assert_equal "acai", Slug::strip_diacritics("açaí")
36
+ end
37
+
38
+ should "strip diacritics correctly " do
39
+ input = "ÀÁÂÃÄÅÆÇÈÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
40
+ output = Slug::strip_diacritics(input).split(//)
41
+ expected = "AAAAAAAECEEEIIIIDNOOOOOOUUUUYThssaaaaaaaeceeeeiiiidnoooooouuuuythy".split(//)
42
+ output.each_index do |i|
43
+ assert_equal expected[i], output[i]
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ context "the Slug class's to_friendly_id method" do
50
+
51
+ should "include the sequence if the sequence is greater than 1" do
52
+ slug = Slug.new(:name => "test", :sequence => 2)
53
+ assert_equal "test--2", slug.to_friendly_id
54
+ end
55
+
56
+ should "not include the sequence if the sequence is 1" do
57
+ slug = Slug.new(:name => "test", :sequence => 1)
58
+ assert_equal "test", slug.to_friendly_id
59
+ end
60
+
61
+ end
62
+
63
+ context "the Slug class's normalize method" do
64
+
65
+ should "should lowercase strings" do
66
+ assert_match /abc/, Slug::normalize("ABC")
67
+ end
68
+
69
+ should "should replace whitespace with dashes" do
70
+ assert_match /a-b/, Slug::normalize("a b")
71
+ end
72
+
73
+ should "should replace 2spaces with 1dash" do
74
+ assert_match /a-b/, Slug::normalize("a b")
75
+ end
76
+
77
+ should "should remove punctuation" do
78
+ assert_match /abc/, Slug::normalize('abc!@#$%^&*•¶§∞¢££¡¿()><?"":;][]\.,/')
79
+ end
80
+
81
+ should "should strip trailing space" do
82
+ assert_match /ab/, Slug::normalize("ab ")
83
+ end
84
+
85
+ should "should strip leading space" do
86
+ assert_match /ab/, Slug::normalize(" ab")
87
+ end
88
+
89
+ should "should strip trailing slashes" do
90
+ assert_match /ab/, Slug::normalize("ab-")
91
+ end
92
+
93
+ should "should strip leading slashes" do
94
+ assert_match /ab/, Slug::normalize("-ab")
95
+ end
96
+
97
+ should "should not modify valid name strings" do
98
+ assert_match /a-b-c-d/, Slug::normalize("a-b-c-d")
99
+ end
100
+
101
+ should "work with non roman chars" do
102
+ assert_equal "検-索", Slug::normalize("検 索")
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,306 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class SluggedModelTest < Test::Unit::TestCase
6
+
7
+ context "A slugged model with default FriendlyId options" do
8
+
9
+ setup do
10
+ Post.friendly_id_options = FriendlyId::DEFAULT_FRIENDLY_ID_OPTIONS.merge(:column => :title, :use_slug => true)
11
+ Post.delete_all
12
+ Person.delete_all
13
+ Slug.delete_all
14
+ Thing.delete_all
15
+ @post = Post.new :title => "Test post", :content => "Test content", :published => true
16
+ @post.save!
17
+ end
18
+
19
+ should "have friendly_id options" do
20
+ assert_not_nil Post.friendly_id_options
21
+ end
22
+
23
+ should "have a slug" do
24
+ assert_not_nil @post.slug
25
+ end
26
+
27
+ should "be findable by its friendly_id" do
28
+ assert Post.find(@post.friendly_id)
29
+ end
30
+
31
+ should "be findable by its regular id" do
32
+ assert Post.find(@post.id)
33
+ end
34
+
35
+ should "be findable by its regular id as a string" do
36
+ assert Post.find(@post.id.to_s)
37
+ end
38
+
39
+ should "not be findable by its id if looking for something else" do
40
+ assert_raises ActiveRecord::RecordNotFound do
41
+ Post.find("#{@post.id}-i-dont-exists")
42
+ end
43
+ end
44
+
45
+ should "generate slug text" do
46
+ post = Post.new :title => "Test post", :content => "Test content"
47
+ assert_not_nil @post.slug_text
48
+ end
49
+
50
+ should "respect finder conditions" do
51
+ assert_raises ActiveRecord::RecordNotFound do
52
+ Post.find(@post.friendly_id, :conditions => "1 = 2")
53
+ end
54
+ end
55
+
56
+ should "raise an error if the friendly_id text is reserved" do
57
+ assert_raises(FriendlyId::SlugGenerationError) do
58
+ Post.create!(:title => "new")
59
+ end
60
+ end
61
+
62
+ should "raise an error if the friendly_id text is blank" do
63
+ assert_raises(FriendlyId::SlugGenerationError) do
64
+ Post.create(:title => "")
65
+ end
66
+ end
67
+
68
+ should "raise an error if the normalized friendly id becomes blank" do
69
+ assert_raises(FriendlyId::SlugGenerationError) do
70
+ post = Post.create!(:title => "-.-")
71
+ end
72
+ end
73
+
74
+ should "not make a new slug unless the friendly_id method value has changed" do
75
+ @post.content = "Changed content"
76
+ @post.save!
77
+ assert_equal 1, @post.slugs.size
78
+ end
79
+
80
+ should "make a new slug if the friendly_id method value has changed" do
81
+ @post.title = "Changed title"
82
+ @post.save!
83
+ assert_equal 2, @post.slugs.size
84
+ end
85
+
86
+ should "have a slug sequence of 1 by default" do
87
+ assert_equal 1, @post.slug.sequence
88
+ end
89
+
90
+ should "increment sequence for duplicate slug names" do
91
+ @post2 = Post.create! :title => @post.title, :content => "Test content for post2"
92
+ assert_equal 2, @post2.slug.sequence
93
+ end
94
+
95
+ should "have a friendly_id that terminates with -- and the slug sequence if the sequence is greater than 1" do
96
+ @post2 = Post.create! :title => @post.title, :content => "Test content for post2"
97
+ assert_match(/--2\z/, @post2.friendly_id)
98
+ end
99
+
100
+ should "allow datetime columns to be used as slugs" do
101
+ assert Event.create(:name => "Test", :event_date => DateTime.now)
102
+ end
103
+
104
+ should "not strip diacritics" do
105
+ @post = Post.new(:title => "¡Feliz año!")
106
+ assert_match(/#{'ñ'}/, @post.slug_text)
107
+ end
108
+
109
+ should "not convert to ASCII" do
110
+ @post = Post.new(:title => "katakana: ゲコゴサザシジ")
111
+ assert_equal "katakana-ゲコゴサザシジ", @post.slug_text
112
+ end
113
+
114
+ should "allow the same friendly_id across models" do
115
+ @person = Person.create!(:name => @post.title)
116
+ assert_equal @person.friendly_id, @post.friendly_id
117
+ end
118
+
119
+ should "truncate slug text longer than the max length" do
120
+ @post = Post.new(:title => "a" * (Post.friendly_id_options[:max_length] + 1))
121
+ assert_equal @post.slug_text.length, Post.friendly_id_options[:max_length]
122
+ end
123
+
124
+ should "truncate slug in 'right way' when slug is unicode" do
125
+ @post = Post.new(:title => "ё" * 100 + 'ю' *(Post.friendly_id_options[:max_length] - 100 + 1))
126
+ assert_equal @post.slug_text.mb_chars[-1], 'ю'
127
+ end
128
+
129
+ should "be able to reuse an old friendly_id without incrementing the sequence" do
130
+ old_title = @post.title
131
+ old_friendly_id = @post.friendly_id
132
+ @post.title = "A changed title"
133
+ @post.save!
134
+ @post.title = old_title
135
+ @post.save!
136
+ assert_equal old_friendly_id, @post.friendly_id
137
+ end
138
+
139
+ should "allow eager loading of slugs" do
140
+ assert_nothing_raised do
141
+ Post.find(@post.friendly_id, :include => :slugs)
142
+ end
143
+ end
144
+
145
+ # This emulates a fairly common issue where id's generated by fixtures are very high.
146
+ should "continue to admit very large ids" do
147
+ Thing.connection.execute("INSERT INTO things (id, name) VALUES (2147483647, 'big')")
148
+ assert Thing.find(2147483647)
149
+ end
150
+
151
+ context "and configured to strip diacritics" do
152
+ setup do
153
+ Post.friendly_id_options = Post.friendly_id_options.merge(:strip_diacritics => true)
154
+ end
155
+
156
+ should "strip diacritics from Roman alphabet based characters" do
157
+ @post = Post.new(:title => "¡Feliz año!")
158
+ assert_no_match(/#{'ñ'}/, @post.slug_text)
159
+ end
160
+ end
161
+
162
+ context "and configured to convert to ASCII" do
163
+ setup do
164
+ Post.friendly_id_options = Post.friendly_id_options.merge(:strip_non_ascii => true)
165
+ end
166
+
167
+ should "strip non-ascii characters" do
168
+ @post = Post.new(:title => "katakana: ゲコゴサザシジ")
169
+ assert_equal "katakana", @post.slug_text
170
+ end
171
+ end
172
+
173
+ context "that doesn't have a slug" do
174
+
175
+ setup do
176
+ @post.slug.destroy
177
+ @post = Post.find(@post.id)
178
+ end
179
+
180
+ should "have a to_param method that returns the id cast to a string" do
181
+ assert_equal @post.id.to_s, @post.to_param
182
+ end
183
+
184
+ end
185
+
186
+ context "when found using its friendly_id" do
187
+ setup do
188
+ @post = Post.find(@post.friendly_id)
189
+ end
190
+
191
+ should "indicate that it was found using the friendly_id" do
192
+ assert @post.found_using_friendly_id?
193
+ end
194
+
195
+ should "not indicate that it has a better id" do
196
+ assert !@post.has_better_id?
197
+ end
198
+
199
+ should "not indicate that it was found using its numeric id" do
200
+ assert !@post.found_using_numeric_id?
201
+ end
202
+
203
+ should "have a finder slug" do
204
+ assert_not_nil @post.finder_slug
205
+ end
206
+
207
+ end
208
+
209
+ context "when found using its regular id" do
210
+ setup do
211
+ @post = Post.find(@post.id)
212
+ end
213
+
214
+ should "indicate that it was not found using the friendly id" do
215
+ assert !@post.found_using_friendly_id?
216
+ end
217
+
218
+ should "indicate that it has a better id" do
219
+ assert @post.has_better_id?
220
+ end
221
+
222
+ should "indicate that it was found using its numeric id" do
223
+ assert @post.found_using_numeric_id?
224
+ end
225
+
226
+ should "not have a finder slug" do
227
+ assert_nil @post.finder_slug
228
+ end
229
+
230
+ end
231
+
232
+ context "when found using an outdated friendly id" do
233
+ setup do
234
+ old_id = @post.friendly_id
235
+ @post.title = "Title changed"
236
+ @post.save!
237
+ @post = Post.find(old_id)
238
+ end
239
+
240
+ should "indicate that it was found using a friendly_id" do
241
+ assert @post.found_using_friendly_id?
242
+ end
243
+
244
+ should "indicate that it has a better id" do
245
+ assert @post.has_better_id?
246
+ end
247
+
248
+ should "not indicate that it was found using its numeric id" do
249
+ assert !@post.found_using_numeric_id?
250
+ end
251
+
252
+ should "should have a finder slug different from its default slug" do
253
+ assert_not_equal @post.slug, @post.finder_slug
254
+ end
255
+
256
+ end
257
+
258
+ context "when using an array as the find argument" do
259
+
260
+ setup do
261
+ @post2 = Post.create!(:title => "another post", :content => "more content", :published => true)
262
+ end
263
+
264
+ should "return results when passed an array of non-friendly ids" do
265
+ assert_equal 2, Post.find([@post.id, @post2.id]).size
266
+ end
267
+
268
+ should "return results when passed an array of friendly ids" do
269
+ assert_equal 2, Post.find([@post.friendly_id, @post2.friendly_id]).size
270
+ end
271
+
272
+ should "return results when searching using a named scope" do
273
+ assert_equal 2, Post.published.find([@post.id, @post2.id]).size
274
+ end
275
+
276
+ should "return results when passed a mixed array of friendly and non-friendly ids" do
277
+ assert_equal 2, Post.find([@post.friendly_id, @post2.id]).size
278
+ end
279
+
280
+ should "return results when passed an array of non-friendly ids, of which one represents a record with multiple slugs" do
281
+ @post2.update_attributes(:title => 'another post [updated]')
282
+ assert_equal 2, Post.find([@post.id, @post2.id]).size
283
+ end
284
+
285
+ should "indicate that the results were found using a friendly_id" do
286
+ @posts = Post.find [@post.friendly_id, @post2.friendly_id]
287
+ @posts.each { |p| assert p.found_using_friendly_id? }
288
+ end
289
+
290
+ should "raise an error when all records are not found" do
291
+ assert_raises(ActiveRecord::RecordNotFound) do
292
+ Post.find([@post.friendly_id, 'non-existant-slug-record'])
293
+ end
294
+ end
295
+
296
+ should "allow eager loading of slugs" do
297
+ assert_nothing_raised do
298
+ Post.find([@post.friendly_id, @post2.friendly_id], :include => :slugs)
299
+ end
300
+ end
301
+
302
+ end
303
+
304
+ end
305
+
306
+ end