nateabbott-friendly-id 2.2.1

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