friendly_id 5.2.4 → 5.5.0
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/stale.yml +17 -0
- data/.github/workflows/test.yml +58 -0
- data/Changelog.md +41 -0
- data/Gemfile +10 -11
- data/README.md +42 -15
- data/Rakefile +24 -27
- data/bench.rb +30 -27
- data/certs/parndt.pem +27 -0
- data/friendly_id.gemspec +28 -27
- data/gemfiles/Gemfile.rails-5.2.rb +11 -16
- data/gemfiles/Gemfile.rails-6.0.rb +22 -0
- data/gemfiles/Gemfile.rails-6.1.rb +22 -0
- data/gemfiles/Gemfile.rails-7.0.rb +22 -0
- data/guide.rb +5 -5
- data/lib/friendly_id/base.rb +61 -68
- data/lib/friendly_id/candidates.rb +9 -11
- data/lib/friendly_id/configuration.rb +8 -8
- data/lib/friendly_id/finder_methods.rb +72 -13
- data/lib/friendly_id/finders.rb +64 -67
- data/lib/friendly_id/history.rb +72 -66
- data/lib/friendly_id/initializer.rb +5 -5
- data/lib/friendly_id/migration.rb +10 -11
- data/lib/friendly_id/object_utils.rb +2 -2
- data/lib/friendly_id/reserved.rb +28 -32
- data/lib/friendly_id/scoped.rb +105 -103
- data/lib/friendly_id/sequentially_slugged/calculator.rb +69 -0
- data/lib/friendly_id/sequentially_slugged.rb +21 -58
- data/lib/friendly_id/simple_i18n.rb +75 -69
- data/lib/friendly_id/slug.rb +1 -2
- data/lib/friendly_id/slug_generator.rb +1 -3
- data/lib/friendly_id/slugged.rb +236 -239
- data/lib/friendly_id/version.rb +1 -1
- data/lib/friendly_id.rb +41 -45
- data/lib/generators/friendly_id_generator.rb +9 -9
- data/test/base_test.rb +10 -13
- data/test/benchmarks/finders.rb +28 -26
- data/test/benchmarks/object_utils.rb +13 -13
- data/test/candidates_test.rb +17 -18
- data/test/configuration_test.rb +7 -11
- data/test/core_test.rb +1 -2
- data/test/databases.yml +7 -4
- data/test/finders_test.rb +52 -5
- data/test/generator_test.rb +16 -26
- data/test/helper.rb +33 -20
- data/test/history_test.rb +116 -72
- data/test/numeric_slug_test.rb +31 -0
- data/test/object_utils_test.rb +0 -2
- data/test/reserved_test.rb +9 -11
- data/test/schema.rb +5 -4
- data/test/scoped_test.rb +26 -15
- data/test/sequentially_slugged_test.rb +107 -33
- data/test/shared.rb +17 -18
- data/test/simple_i18n_test.rb +23 -13
- data/test/slugged_test.rb +254 -78
- data/test/sti_test.rb +19 -21
- data.tar.gz.sig +0 -0
- metadata +49 -19
- metadata.gz.sig +1 -0
- data/.travis.yml +0 -57
- data/gemfiles/Gemfile.rails-4.0.rb +0 -30
- data/gemfiles/Gemfile.rails-4.1.rb +0 -29
- data/gemfiles/Gemfile.rails-4.2.rb +0 -28
- data/gemfiles/Gemfile.rails-5.0.rb +0 -28
- data/gemfiles/Gemfile.rails-5.1.rb +0 -27
data/test/helper.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
require "bundler/setup"
|
2
2
|
|
3
|
-
if ENV[
|
4
|
-
require
|
5
|
-
if ENV[
|
6
|
-
require
|
3
|
+
if ENV["COVERALLS"] || ENV["COVERAGE"]
|
4
|
+
require "simplecov"
|
5
|
+
if ENV["COVERALLS"]
|
6
|
+
require "coveralls"
|
7
7
|
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
8
8
|
end
|
9
9
|
SimpleCov.start do
|
10
|
-
add_filter
|
11
|
-
add_filter
|
10
|
+
add_filter "test"
|
11
|
+
add_filter "friendly_id/migration"
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
begin
|
16
|
-
require
|
16
|
+
require "minitest"
|
17
17
|
rescue LoadError
|
18
|
-
require
|
18
|
+
require "minitest/unit"
|
19
19
|
end
|
20
20
|
|
21
21
|
begin
|
@@ -24,9 +24,10 @@ rescue NameError
|
|
24
24
|
TestCaseClass = MiniTest::Unit::TestCase
|
25
25
|
end
|
26
26
|
|
27
|
-
require "mocha/
|
27
|
+
require "mocha/minitest"
|
28
28
|
require "active_record"
|
29
|
-
require
|
29
|
+
require "active_support/core_ext/time/conversions"
|
30
|
+
require "erb"
|
30
31
|
|
31
32
|
I18n.enforce_available_locales = false
|
32
33
|
|
@@ -38,29 +39,32 @@ if ENV["LOG"]
|
|
38
39
|
ActiveRecord::Base.logger = Logger.new($stdout)
|
39
40
|
end
|
40
41
|
|
41
|
-
if ActiveSupport::VERSION::STRING >=
|
42
|
+
if ActiveSupport::VERSION::STRING >= "4.2"
|
42
43
|
ActiveSupport.test_order = :random
|
43
44
|
end
|
44
45
|
|
45
46
|
module FriendlyId
|
46
47
|
module Test
|
47
|
-
|
48
48
|
def self.included(base)
|
49
49
|
if Minitest.respond_to?(:autorun)
|
50
50
|
Minitest.autorun
|
51
51
|
else
|
52
|
-
require
|
52
|
+
require "minitest/autorun"
|
53
53
|
end
|
54
54
|
rescue LoadError
|
55
55
|
end
|
56
56
|
|
57
57
|
def transaction
|
58
|
-
ActiveRecord::Base.transaction
|
58
|
+
ActiveRecord::Base.transaction do
|
59
|
+
yield
|
60
|
+
|
61
|
+
raise ActiveRecord::Rollback
|
62
|
+
end
|
59
63
|
end
|
60
64
|
|
61
65
|
def with_instance_of(*args)
|
62
66
|
model_class = args.shift
|
63
|
-
args[0] ||= {:
|
67
|
+
args[0] ||= {name: "a b c"}
|
64
68
|
transaction { yield model_class.create!(*args) }
|
65
69
|
end
|
66
70
|
|
@@ -69,8 +73,11 @@ module FriendlyId
|
|
69
73
|
|
70
74
|
def connect
|
71
75
|
version = ActiveRecord::VERSION::STRING
|
72
|
-
|
73
|
-
|
76
|
+
engine = begin
|
77
|
+
RUBY_ENGINE
|
78
|
+
rescue
|
79
|
+
"ruby"
|
80
|
+
end
|
74
81
|
|
75
82
|
ActiveRecord::Base.establish_connection config[driver]
|
76
83
|
message = "Using #{engine} #{RUBY_VERSION} AR #{version} with #{driver}"
|
@@ -86,11 +93,17 @@ module FriendlyId
|
|
86
93
|
end
|
87
94
|
|
88
95
|
def config
|
89
|
-
@config ||= YAML
|
96
|
+
@config ||= YAML.safe_load(
|
97
|
+
ERB.new(
|
98
|
+
File.read(File.expand_path("../databases.yml", __FILE__))
|
99
|
+
).result
|
100
|
+
)
|
90
101
|
end
|
91
102
|
|
92
103
|
def driver
|
93
|
-
(
|
104
|
+
db_driver = ENV.fetch("DB", "sqlite3").downcase
|
105
|
+
db_driver = "postgres" if %w[postgresql pg].include?(db_driver)
|
106
|
+
db_driver
|
94
107
|
end
|
95
108
|
|
96
109
|
def in_memory?
|
@@ -109,4 +122,4 @@ end
|
|
109
122
|
require "schema"
|
110
123
|
require "shared"
|
111
124
|
FriendlyId::Test::Database.connect
|
112
|
-
at_exit {ActiveRecord::Base.connection.disconnect!}
|
125
|
+
at_exit { ActiveRecord::Base.connection.disconnect! }
|
data/test/history_test.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require "helper"
|
2
2
|
|
3
3
|
class HistoryTest < TestCaseClass
|
4
|
-
|
5
4
|
include FriendlyId::Test
|
6
5
|
include FriendlyId::Test::Shared::Core
|
7
6
|
|
8
7
|
class Manual < ActiveRecord::Base
|
9
8
|
extend FriendlyId
|
10
|
-
friendly_id :name, :
|
9
|
+
friendly_id :name, use: [:slugged, :history]
|
11
10
|
end
|
12
11
|
|
13
12
|
def model_class
|
@@ -15,7 +14,7 @@ class HistoryTest < TestCaseClass
|
|
15
14
|
end
|
16
15
|
|
17
16
|
test "should insert record in slugs table on create" do
|
18
|
-
with_instance_of(model_class) {|record| assert record.slugs.any?}
|
17
|
+
with_instance_of(model_class) { |record| assert record.slugs.any? }
|
19
18
|
end
|
20
19
|
|
21
20
|
test "should not create new slug record if friendly_id is not changed" do
|
@@ -52,8 +51,9 @@ class HistoryTest < TestCaseClass
|
|
52
51
|
|
53
52
|
test "should create slug records on each change" do
|
54
53
|
transaction do
|
55
|
-
|
54
|
+
model_class.create! name: "hello"
|
56
55
|
assert_equal 1, FriendlyId::Slug.count
|
56
|
+
|
57
57
|
record = model_class.friendly.find("hello")
|
58
58
|
record.name = "hello again"
|
59
59
|
record.slug = nil
|
@@ -65,8 +65,7 @@ class HistoryTest < TestCaseClass
|
|
65
65
|
test "should not be read only when found by slug" do
|
66
66
|
with_instance_of(model_class) do |record|
|
67
67
|
refute model_class.friendly.find(record.friendly_id).readonly?
|
68
|
-
assert record.
|
69
|
-
assert record.update_attributes name: 'foo'
|
68
|
+
assert record.update name: "foo"
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
@@ -81,38 +80,60 @@ class HistoryTest < TestCaseClass
|
|
81
80
|
|
82
81
|
test "should handle renames" do
|
83
82
|
with_instance_of(model_class) do |record|
|
84
|
-
record.name =
|
83
|
+
record.name = "x"
|
85
84
|
record.slug = nil
|
86
85
|
assert record.save
|
87
|
-
record.name =
|
86
|
+
record.name = "y"
|
88
87
|
record.slug = nil
|
89
88
|
assert record.save
|
90
|
-
record.name =
|
89
|
+
record.name = "x"
|
90
|
+
record.slug = nil
|
91
|
+
assert record.save
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
test "should maintain history even if current slug is not the most recent one" do
|
96
|
+
with_instance_of(model_class) do |record|
|
97
|
+
record.name = "current"
|
98
|
+
assert record.save
|
99
|
+
|
100
|
+
# this feels like a hack. only thing i can get to work with the HistoryTestWithSti
|
101
|
+
# test cases. (Editorialist vs Journalist.)
|
102
|
+
sluggable_type = FriendlyId::Slug.first.sluggable_type
|
103
|
+
# create several slugs for record
|
104
|
+
# current slug does not have max id
|
105
|
+
FriendlyId::Slug.delete_all
|
106
|
+
FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: "current")
|
107
|
+
FriendlyId::Slug.create(sluggable_type: sluggable_type, sluggable_id: record.id, slug: "outdated")
|
108
|
+
|
109
|
+
record.reload
|
91
110
|
record.slug = nil
|
92
111
|
assert record.save
|
112
|
+
|
113
|
+
assert_equal 2, FriendlyId::Slug.count
|
93
114
|
end
|
94
115
|
end
|
95
116
|
|
96
117
|
test "should not create new slugs that match old slugs" do
|
97
118
|
transaction do
|
98
|
-
first_record = model_class.create! :
|
119
|
+
first_record = model_class.create! name: "foo"
|
99
120
|
first_record.name = "bar"
|
100
121
|
first_record.save!
|
101
|
-
second_record = model_class.create! :
|
122
|
+
second_record = model_class.create! name: "foo"
|
102
123
|
assert second_record.slug != "foo"
|
103
124
|
assert_match(/foo-.+/, second_record.slug)
|
104
125
|
end
|
105
126
|
end
|
106
127
|
|
107
|
-
test
|
128
|
+
test "should not fail when updating historic slugs" do
|
108
129
|
transaction do
|
109
|
-
first_record = model_class.create! :
|
110
|
-
second_record = model_class.create! :
|
130
|
+
first_record = model_class.create! name: "foo"
|
131
|
+
second_record = model_class.create! name: "another"
|
111
132
|
|
112
|
-
second_record.
|
133
|
+
second_record.update name: "foo", slug: nil
|
113
134
|
assert_match(/foo-.*/, second_record.slug)
|
114
135
|
|
115
|
-
first_record.
|
136
|
+
first_record.update name: "another", slug: nil
|
116
137
|
assert_match(/another-.*/, first_record.slug)
|
117
138
|
end
|
118
139
|
end
|
@@ -123,25 +144,23 @@ class HistoryTest < TestCaseClass
|
|
123
144
|
second_record = model_class.create! name: "bar"
|
124
145
|
|
125
146
|
first_record.update! slug: "not_foo"
|
126
|
-
second_record.update! slug: "foo" #now both records have used foo; second_record most recently
|
147
|
+
second_record.update! slug: "foo" # now both records have used foo; second_record most recently
|
127
148
|
second_record.update! slug: "not_bar"
|
128
149
|
|
129
150
|
assert_equal model_class.friendly.find("foo"), second_record
|
130
151
|
end
|
131
152
|
end
|
132
153
|
|
133
|
-
test
|
154
|
+
test "should name table according to prefix and suffix" do
|
134
155
|
transaction do
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
FriendlyId::Slug.table_name = without_prefix
|
144
|
-
end
|
156
|
+
prefix = "prefix_"
|
157
|
+
without_prefix = FriendlyId::Slug.table_name
|
158
|
+
ActiveRecord::Base.table_name_prefix = prefix
|
159
|
+
FriendlyId::Slug.reset_table_name
|
160
|
+
assert_equal prefix + without_prefix, FriendlyId::Slug.table_name
|
161
|
+
ensure
|
162
|
+
ActiveRecord::Base.table_name_prefix = ""
|
163
|
+
FriendlyId::Slug.table_name = without_prefix
|
145
164
|
end
|
146
165
|
end
|
147
166
|
end
|
@@ -149,7 +168,7 @@ end
|
|
149
168
|
class HistoryTestWithAutomaticSlugRegeneration < HistoryTest
|
150
169
|
class Manual < ActiveRecord::Base
|
151
170
|
extend FriendlyId
|
152
|
-
friendly_id :name, :
|
171
|
+
friendly_id :name, use: [:slugged, :history]
|
153
172
|
|
154
173
|
def should_generate_new_friendly_id?
|
155
174
|
slug.blank? or name_changed?
|
@@ -160,62 +179,61 @@ class HistoryTestWithAutomaticSlugRegeneration < HistoryTest
|
|
160
179
|
Manual
|
161
180
|
end
|
162
181
|
|
163
|
-
test
|
164
|
-
with_instance_of(model_class, name:
|
165
|
-
record.name =
|
182
|
+
test "should allow reversion back to a previously used slug" do
|
183
|
+
with_instance_of(model_class, name: "foo") do |record|
|
184
|
+
record.name = "bar"
|
166
185
|
record.save!
|
167
|
-
assert_equal
|
168
|
-
record.name =
|
186
|
+
assert_equal "bar", record.friendly_id
|
187
|
+
record.name = "foo"
|
169
188
|
record.save!
|
170
|
-
assert_equal
|
189
|
+
assert_equal "foo", record.friendly_id
|
171
190
|
end
|
172
191
|
end
|
173
192
|
end
|
174
193
|
|
175
194
|
class DependentDestroyTest < TestCaseClass
|
176
|
-
|
177
195
|
include FriendlyId::Test
|
178
196
|
|
179
197
|
class FalseManual < ActiveRecord::Base
|
180
|
-
self.table_name =
|
198
|
+
self.table_name = "manuals"
|
181
199
|
|
182
200
|
extend FriendlyId
|
183
|
-
friendly_id :name, :
|
201
|
+
friendly_id :name, use: :history, dependent: false
|
184
202
|
end
|
185
203
|
|
186
204
|
class DefaultManual < ActiveRecord::Base
|
187
|
-
self.table_name =
|
205
|
+
self.table_name = "manuals"
|
188
206
|
|
189
207
|
extend FriendlyId
|
190
|
-
friendly_id :name, :
|
208
|
+
friendly_id :name, use: :history
|
191
209
|
end
|
192
210
|
|
193
|
-
test
|
211
|
+
test "should allow disabling of dependent destroy" do
|
194
212
|
transaction do
|
195
|
-
assert FriendlyId::Slug.find_by_slug(
|
196
|
-
l = FalseManual.create! :
|
197
|
-
assert FriendlyId::Slug.find_by_slug(
|
213
|
+
assert FriendlyId::Slug.find_by_slug("foo").nil?
|
214
|
+
l = FalseManual.create! name: "foo"
|
215
|
+
assert FriendlyId::Slug.find_by_slug("foo").present?
|
198
216
|
l.destroy
|
199
|
-
assert FriendlyId::Slug.find_by_slug(
|
217
|
+
assert FriendlyId::Slug.find_by_slug("foo").present?
|
200
218
|
end
|
201
219
|
end
|
202
220
|
|
203
|
-
test
|
221
|
+
test "should dependently destroy by default" do
|
204
222
|
transaction do
|
205
|
-
assert FriendlyId::Slug.find_by_slug(
|
206
|
-
l = DefaultManual.create! :
|
207
|
-
assert FriendlyId::Slug.find_by_slug(
|
223
|
+
assert FriendlyId::Slug.find_by_slug("baz").nil?
|
224
|
+
l = DefaultManual.create! name: "baz"
|
225
|
+
assert FriendlyId::Slug.find_by_slug("baz").present?
|
208
226
|
l.destroy
|
209
|
-
assert FriendlyId::Slug.find_by_slug(
|
227
|
+
assert FriendlyId::Slug.find_by_slug("baz").nil?
|
210
228
|
end
|
211
229
|
end
|
212
230
|
end
|
213
231
|
|
214
|
-
if ActiveRecord::VERSION::STRING >=
|
232
|
+
if ActiveRecord::VERSION::STRING >= "5.0"
|
215
233
|
class HistoryTestWithParanoidDeletes < HistoryTest
|
216
234
|
class ParanoidRecord < ActiveRecord::Base
|
217
235
|
extend FriendlyId
|
218
|
-
friendly_id :name, :
|
236
|
+
friendly_id :name, use: :history, dependent: false
|
219
237
|
|
220
238
|
default_scope { where(deleted_at: nil) }
|
221
239
|
end
|
@@ -224,19 +242,19 @@ if ActiveRecord::VERSION::STRING >= '5.0'
|
|
224
242
|
ParanoidRecord
|
225
243
|
end
|
226
244
|
|
227
|
-
test
|
245
|
+
test "slug should have a sluggable even when soft deleted by a library" do
|
228
246
|
transaction do
|
229
|
-
assert FriendlyId::Slug.find_by_slug(
|
230
|
-
record = model_class.create(name:
|
231
|
-
assert FriendlyId::Slug.find_by_slug(
|
247
|
+
assert FriendlyId::Slug.find_by_slug("paranoid").nil?
|
248
|
+
record = model_class.create(name: "paranoid")
|
249
|
+
assert FriendlyId::Slug.find_by_slug("paranoid").present?
|
232
250
|
|
233
|
-
record.
|
251
|
+
record.update deleted_at: Time.now
|
234
252
|
|
235
|
-
orphan_slug = FriendlyId::Slug.find_by_slug(
|
236
|
-
assert orphan_slug.present?,
|
253
|
+
orphan_slug = FriendlyId::Slug.find_by_slug("paranoid")
|
254
|
+
assert orphan_slug.present?, "Orphaned slug should exist"
|
237
255
|
|
238
256
|
assert orphan_slug.valid?, "Errors: #{orphan_slug.errors.full_messages}"
|
239
|
-
assert orphan_slug.sluggable.present?,
|
257
|
+
assert orphan_slug.sluggable.present?, "Orphaned slug should still find corresponding paranoid sluggable"
|
240
258
|
end
|
241
259
|
end
|
242
260
|
end
|
@@ -245,7 +263,7 @@ end
|
|
245
263
|
class HistoryTestWithSti < HistoryTest
|
246
264
|
class Journalist < ActiveRecord::Base
|
247
265
|
extend FriendlyId
|
248
|
-
friendly_id :name, :
|
266
|
+
friendly_id :name, use: [:slugged, :history]
|
249
267
|
end
|
250
268
|
|
251
269
|
class Editorialist < Journalist
|
@@ -259,16 +277,15 @@ end
|
|
259
277
|
class HistoryTestWithFriendlyFinders < HistoryTest
|
260
278
|
class Journalist < ActiveRecord::Base
|
261
279
|
extend FriendlyId
|
262
|
-
friendly_id :name, :
|
280
|
+
friendly_id :name, use: [:slugged, :finders, :history]
|
263
281
|
end
|
264
282
|
|
265
283
|
class Restaurant < ActiveRecord::Base
|
266
284
|
extend FriendlyId
|
267
285
|
belongs_to :city
|
268
|
-
friendly_id :name, :
|
286
|
+
friendly_id :name, use: [:slugged, :history, :finders]
|
269
287
|
end
|
270
288
|
|
271
|
-
|
272
289
|
test "should be findable by old slugs" do
|
273
290
|
[Journalist, Restaurant].each do |model_class|
|
274
291
|
with_instance_of(model_class) do |record|
|
@@ -297,7 +314,7 @@ class HistoryTestWithFindersBeforeHistory < HistoryTest
|
|
297
314
|
|
298
315
|
belongs_to :novelist
|
299
316
|
|
300
|
-
friendly_id :name, :
|
317
|
+
friendly_id :name, use: [:finders, :history]
|
301
318
|
|
302
319
|
def should_generate_new_friendly_id?
|
303
320
|
slug.blank? || name_changed?
|
@@ -306,8 +323,8 @@ class HistoryTestWithFindersBeforeHistory < HistoryTest
|
|
306
323
|
|
307
324
|
test "should be findable by old slug through has_many association" do
|
308
325
|
transaction do
|
309
|
-
novelist = Novelist.create!(:
|
310
|
-
novel = novelist.novels.create(:
|
326
|
+
novelist = Novelist.create!(name: "Stephen King")
|
327
|
+
novel = novelist.novels.create(name: "Rita Hayworth and Shawshank Redemption")
|
311
328
|
slug = novel.slug
|
312
329
|
novel.name = "Shawshank Redemption"
|
313
330
|
novel.save!
|
@@ -324,7 +341,7 @@ end
|
|
324
341
|
class Restaurant < ActiveRecord::Base
|
325
342
|
extend FriendlyId
|
326
343
|
belongs_to :city
|
327
|
-
friendly_id :name, :
|
344
|
+
friendly_id :name, use: [:scoped, :history], scope: :city
|
328
345
|
end
|
329
346
|
|
330
347
|
class ScopedHistoryTest < TestCaseClass
|
@@ -368,21 +385,48 @@ class ScopedHistoryTest < TestCaseClass
|
|
368
385
|
record.slug = nil
|
369
386
|
record.save!
|
370
387
|
|
371
|
-
second_record = model_class.create! :
|
388
|
+
second_record = model_class.create! city: city, name: "x"
|
372
389
|
assert_match(/x-.+/, second_record.friendly_id)
|
373
390
|
|
374
|
-
third_record = model_class.create! :
|
391
|
+
third_record = model_class.create! city: city, name: "y"
|
375
392
|
assert_match(/y-.+/, third_record.friendly_id)
|
376
393
|
end
|
377
394
|
end
|
378
395
|
end
|
379
396
|
|
397
|
+
test "should record history when scope changes" do
|
398
|
+
transaction do
|
399
|
+
city1 = City.create!
|
400
|
+
city2 = City.create!
|
401
|
+
with_instance_of(Restaurant) do |record|
|
402
|
+
record.name = "x"
|
403
|
+
record.slug = nil
|
404
|
+
|
405
|
+
record.city = city1
|
406
|
+
record.save!
|
407
|
+
assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
|
408
|
+
assert_equal("x", record.slugs.reload.first.slug)
|
409
|
+
|
410
|
+
record.city = city2
|
411
|
+
record.save!
|
412
|
+
assert_equal("city_id:#{city2.id}", record.slugs.reload.first.scope)
|
413
|
+
|
414
|
+
record.name = "y"
|
415
|
+
record.slug = nil
|
416
|
+
record.city = city1
|
417
|
+
record.save!
|
418
|
+
assert_equal("city_id:#{city1.id}", record.slugs.reload.first.scope)
|
419
|
+
assert_equal("y", record.slugs.reload.first.slug)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
380
424
|
test "should allow equal slugs in different scopes" do
|
381
425
|
transaction do
|
382
426
|
city = City.create!
|
383
427
|
second_city = City.create!
|
384
|
-
record = model_class.create! :
|
385
|
-
second_record = model_class.create! :
|
428
|
+
record = model_class.create! city: city, name: "x"
|
429
|
+
second_record = model_class.create! city: second_city, name: "x"
|
386
430
|
|
387
431
|
assert_equal record.slug, second_record.slug
|
388
432
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class NumericSlugTest < TestCaseClass
|
4
|
+
include FriendlyId::Test
|
5
|
+
include FriendlyId::Test::Shared::Core
|
6
|
+
|
7
|
+
def model_class
|
8
|
+
Article
|
9
|
+
end
|
10
|
+
|
11
|
+
test "should generate numeric slugs" do
|
12
|
+
transaction do
|
13
|
+
record = model_class.create! name: "123"
|
14
|
+
assert_equal "123", record.slug
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
test "should find by numeric slug" do
|
19
|
+
transaction do
|
20
|
+
record = model_class.create! name: "123"
|
21
|
+
assert_equal model_class.friendly.find("123").id, record.id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
test "should exist? by numeric slug" do
|
26
|
+
transaction do
|
27
|
+
model_class.create! name: "123"
|
28
|
+
assert model_class.friendly.exists?("123")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/test/object_utils_test.rb
CHANGED
data/test/reserved_test.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
require "helper"
|
2
2
|
|
3
3
|
class ReservedTest < TestCaseClass
|
4
|
-
|
5
4
|
include FriendlyId::Test
|
6
5
|
|
7
6
|
class Journalist < ActiveRecord::Base
|
8
7
|
extend FriendlyId
|
9
|
-
friendly_id :slug_candidates, :
|
8
|
+
friendly_id :slug_candidates, use: [:slugged, :reserved], reserved_words: %w[new edit]
|
10
9
|
|
11
10
|
after_validation :move_friendly_id_error_to_name
|
12
11
|
|
@@ -24,9 +23,9 @@ class ReservedTest < TestCaseClass
|
|
24
23
|
end
|
25
24
|
|
26
25
|
test "should reserve words" do
|
27
|
-
%w
|
26
|
+
%w[new edit NEW Edit].each do |word|
|
28
27
|
transaction do
|
29
|
-
assert_raises(ActiveRecord::RecordInvalid) {model_class.create! :
|
28
|
+
assert_raises(ActiveRecord::RecordInvalid) { model_class.create! name: word }
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
@@ -43,7 +42,7 @@ class ReservedTest < TestCaseClass
|
|
43
42
|
|
44
43
|
test "should reject reserved candidates" do
|
45
44
|
transaction do
|
46
|
-
record = model_class.new(:
|
45
|
+
record = model_class.new(name: "new")
|
47
46
|
def record.slug_candidates
|
48
47
|
[:name, "foo"]
|
49
48
|
end
|
@@ -54,22 +53,21 @@ class ReservedTest < TestCaseClass
|
|
54
53
|
|
55
54
|
test "should be invalid if all candidates are reserved" do
|
56
55
|
transaction do
|
57
|
-
record = model_class.new(:
|
56
|
+
record = model_class.new(name: "new")
|
58
57
|
def record.slug_candidates
|
59
58
|
["edit", "new"]
|
60
59
|
end
|
61
|
-
assert_raises(ActiveRecord::RecordInvalid) {record.save!}
|
60
|
+
assert_raises(ActiveRecord::RecordInvalid) { record.save! }
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
65
64
|
test "should optionally treat reserved words as conflict" do
|
66
65
|
klass = Class.new(model_class) do
|
67
|
-
friendly_id :slug_candidates, :
|
66
|
+
friendly_id :slug_candidates, use: [:slugged, :reserved], reserved_words: %w[new edit], treat_reserved_as_conflict: true
|
68
67
|
end
|
69
68
|
|
70
|
-
with_instance_of(klass, name:
|
71
|
-
assert_match(/new-([0-9a-z]
|
69
|
+
with_instance_of(klass, name: "new") do |record|
|
70
|
+
assert_match(/new-([0-9a-z]+-){4}[0-9a-z]+\z/, record.slug)
|
72
71
|
end
|
73
72
|
end
|
74
|
-
|
75
73
|
end
|
data/test/schema.rb
CHANGED
@@ -25,7 +25,7 @@ module FriendlyId
|
|
25
25
|
|
26
26
|
tables.each do |table_name|
|
27
27
|
create_table table_name do |t|
|
28
|
-
t.string
|
28
|
+
t.string :name
|
29
29
|
t.boolean :active
|
30
30
|
end
|
31
31
|
end
|
@@ -41,7 +41,7 @@ module FriendlyId
|
|
41
41
|
|
42
42
|
slugged_tables.each do |table_name|
|
43
43
|
add_column table_name, :slug, :string
|
44
|
-
add_index
|
44
|
+
add_index table_name, :slug, unique: true if table_name != "novels"
|
45
45
|
end
|
46
46
|
|
47
47
|
scoped_tables.each do |table_name|
|
@@ -57,7 +57,7 @@ module FriendlyId
|
|
57
57
|
# This will be used to test scopes
|
58
58
|
add_column :novels, :novelist_id, :integer
|
59
59
|
add_column :novels, :publisher_id, :integer
|
60
|
-
add_index :novels, [:slug, :publisher_id, :novelist_id], :
|
60
|
+
add_index :novels, [:slug, :publisher_id, :novelist_id], unique: true
|
61
61
|
|
62
62
|
# This will be used to test column name quoting
|
63
63
|
add_column :journalists, "strange name", :string
|
@@ -69,6 +69,7 @@ module FriendlyId
|
|
69
69
|
add_column :journalists, "slug_en", :string
|
70
70
|
add_column :journalists, "slug_es", :string
|
71
71
|
add_column :journalists, "slug_de", :string
|
72
|
+
add_column :journalists, "slug_fr_ca", :string
|
72
73
|
|
73
74
|
# This will be used to test relationships
|
74
75
|
add_column :books, :author_id, :integer
|
@@ -77,7 +78,7 @@ module FriendlyId
|
|
77
78
|
add_column :restaurants, :city_id, :integer
|
78
79
|
|
79
80
|
# Used to test candidates
|
80
|
-
add_column :cities, :code, :string, :
|
81
|
+
add_column :cities, :code, :string, limit: 3
|
81
82
|
|
82
83
|
# Used as a non-default slug_column
|
83
84
|
add_column :authors, :subdomain, :string
|