friendly_id 4.1.0.beta.1 → 5.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +7 -12
- data/Changelog.md +4 -0
- data/Gemfile +2 -2
- data/Guide.rdoc +34 -63
- data/README.md +116 -60
- data/Rakefile +5 -1
- data/WhatsNew.md +2 -94
- data/friendly_id.gemspec +10 -20
- data/gemfiles/Gemfile.rails-4.0.rb +19 -0
- data/lib/friendly_id.rb +11 -6
- data/lib/friendly_id/.gitattributes +1 -0
- data/lib/friendly_id/base.rb +6 -65
- data/lib/friendly_id/candidates.rb +41 -0
- data/lib/friendly_id/configuration.rb +10 -6
- data/lib/friendly_id/history.rb +22 -60
- data/lib/friendly_id/object_utils.rb +1 -1
- data/lib/friendly_id/scoped.rb +40 -34
- data/lib/friendly_id/scopes.rb +28 -0
- data/lib/friendly_id/simple_i18n.rb +13 -3
- data/lib/friendly_id/slug.rb +0 -1
- data/lib/friendly_id/slug_generator.rb +9 -68
- data/lib/friendly_id/slugged.rb +22 -34
- data/lib/friendly_id/version.rb +3 -0
- data/lib/generators/friendly_id_generator.rb +5 -4
- data/test/helper.rb +1 -1
- data/test/history_test.rb +63 -56
- data/test/object_utils_test.rb +0 -1
- data/test/schema.rb +1 -10
- data/test/shared.rb +27 -23
- data/test/simple_i18n_test.rb +3 -2
- data/test/slugged_test.rb +17 -48
- data/test/sti_test.rb +2 -2
- metadata +40 -103
- data/gemfiles/Gemfile.rails-3.0.rb +0 -21
- data/gemfiles/Gemfile.rails-3.1.rb +0 -22
- data/gemfiles/Gemfile.rails-3.2.rb +0 -22
- data/lib/friendly_id/finder_methods.rb +0 -35
- data/lib/friendly_id/globalize.rb +0 -115
- data/test/globalize_test.rb +0 -57
@@ -8,7 +8,7 @@ module FriendlyId
|
|
8
8
|
# be sufficient to avoid conflicts with other libraries.
|
9
9
|
module ObjectUtils
|
10
10
|
|
11
|
-
# True
|
11
|
+
# True if the id is definitely friendly, false if definitely unfriendly,
|
12
12
|
# else nil.
|
13
13
|
#
|
14
14
|
# An object is considired "definitely unfriendly" if its class is or
|
data/lib/friendly_id/scoped.rb
CHANGED
@@ -28,11 +28,33 @@ This allows, for example, two restaurants in different cities to have the slug
|
|
28
28
|
City.find("chicago").restaurants.find("joes-diner")
|
29
29
|
|
30
30
|
Without :scoped in this case, one of the restaurants would have the slug
|
31
|
-
+joes-diner+ and the other would have +joes-diner
|
31
|
+
+joes-diner+ and the other would have +joes-diner-f9f3789a-daec-4156-af1d-fab81aa16ee5+.
|
32
32
|
|
33
33
|
The value for the +:scope+ option can be the name of a +belongs_to+ relation, or
|
34
34
|
a column.
|
35
35
|
|
36
|
+
Additionally, the +:scope+ option can receive an array of scope values:
|
37
|
+
|
38
|
+
class Cuisine < ActiveRecord::Base
|
39
|
+
extend FriendlyId
|
40
|
+
has_many :restaurants
|
41
|
+
friendly_id :name, :use => :slugged
|
42
|
+
end
|
43
|
+
|
44
|
+
class City < ActiveRecord::Base
|
45
|
+
extend FriendlyId
|
46
|
+
has_many :restaurants
|
47
|
+
friendly_id :name, :use => :slugged
|
48
|
+
end
|
49
|
+
|
50
|
+
class Restaurant < ActiveRecord::Base
|
51
|
+
extend FriendlyId
|
52
|
+
belongs_to :city
|
53
|
+
friendly_id :name, :use => :scoped, :scope => [:city, :cuisine]
|
54
|
+
end
|
55
|
+
|
56
|
+
All supplied values will be used to determine scope.
|
57
|
+
|
36
58
|
=== Finding Records by Friendly ID
|
37
59
|
|
38
60
|
If you are using scopes your friendly ids may not be unique, so a simple find
|
@@ -81,14 +103,17 @@ an example of one way to set this up:
|
|
81
103
|
=end
|
82
104
|
module Scoped
|
83
105
|
|
106
|
+
# FriendlyId::Config.use will invoke this method when present, to allow
|
107
|
+
# loading dependent modules prior to overriding them when necessary.
|
108
|
+
def self.setup(model_class)
|
109
|
+
model_class.friendly_id_config.use :slugged
|
110
|
+
end
|
84
111
|
|
85
112
|
# Sets up behavior and configuration options for FriendlyId's scoped slugs
|
86
113
|
# feature.
|
87
114
|
def self.included(model_class)
|
88
|
-
model_class.
|
89
|
-
include Slugged unless self < Slugged
|
115
|
+
model_class.class_eval do
|
90
116
|
friendly_id_config.class.send :include, Configuration
|
91
|
-
friendly_id_config.slug_generator_class.send :include, SlugGenerator
|
92
117
|
end
|
93
118
|
end
|
94
119
|
|
@@ -96,6 +121,15 @@ an example of one way to set this up:
|
|
96
121
|
friendly_id_config.scope_columns.sort.map { |column| "#{column}:#{send(column)}" }.join(",")
|
97
122
|
end
|
98
123
|
|
124
|
+
def slug_generator
|
125
|
+
relation = self.class.unscoped.friendly
|
126
|
+
friendly_id_config.scope_columns.each do |column|
|
127
|
+
relation = relation.where(column => send(column))
|
128
|
+
end
|
129
|
+
friendly_id_config.slug_generator_class.new(relation)
|
130
|
+
end
|
131
|
+
private :slug_generator
|
132
|
+
|
99
133
|
# This module adds the +:scope+ configuration option to
|
100
134
|
# {FriendlyId::Configuration FriendlyId::Configuration}.
|
101
135
|
module Configuration
|
@@ -122,36 +156,8 @@ an example of one way to set this up:
|
|
122
156
|
|
123
157
|
private
|
124
158
|
|
125
|
-
|
126
|
-
|
127
|
-
model_class.reflections[scope].try(:primary_key_name)
|
128
|
-
end
|
129
|
-
else
|
130
|
-
def reflection_foreign_key(scope)
|
131
|
-
model_class.reflections[scope].try(:foreign_key)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# This module overrides {FriendlyId::SlugGenerator#conflict} to consider
|
137
|
-
# scope, to avoid adding sequences to slugs under different scopes.
|
138
|
-
module SlugGenerator
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def conflict
|
143
|
-
if friendly_id_config.uses?(:history)
|
144
|
-
# When using the :history module +conflicts+ already returns only real conflicts, so there's no need to check
|
145
|
-
# for the scope columns again
|
146
|
-
conflicts.first
|
147
|
-
else
|
148
|
-
columns = friendly_id_config.scope_columns
|
149
|
-
matched = columns.inject(conflicts) do |memo, column|
|
150
|
-
memo.where(column => sluggable.send(column))
|
151
|
-
end
|
152
|
-
|
153
|
-
matched.first
|
154
|
-
end
|
159
|
+
def reflection_foreign_key(scope)
|
160
|
+
model_class.reflections[scope].try(:foreign_key)
|
155
161
|
end
|
156
162
|
end
|
157
163
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
module Scopes
|
3
|
+
|
4
|
+
def self.extended(model_class)
|
5
|
+
model_class.scope :friendly, ->{model_class.all} do
|
6
|
+
def find(*args)
|
7
|
+
id = args.first
|
8
|
+
return super if args.count != 1 || id.unfriendly_id?
|
9
|
+
find_by_friendly_id(id) or super
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?(conditions = :none)
|
13
|
+
return super if conditions.unfriendly_id? || conditions.unfriendly_id?.nil?
|
14
|
+
exists_by_friendly_id?(conditions)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_by_friendly_id(id)
|
20
|
+
where(friendly_id_config.query_field => id).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def exists_by_friendly_id?(id)
|
24
|
+
where(friendly_id_config.query_field => id).exists?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -17,7 +17,7 @@ must also include the locale in its name.
|
|
17
17
|
|
18
18
|
This module is most suitable to applications that need to support few locales.
|
19
19
|
If you need to support two or more locales, you may wish to use the
|
20
|
-
|
20
|
+
friendly_id_globalize gem instead.
|
21
21
|
|
22
22
|
=== Example migration
|
23
23
|
|
@@ -70,9 +70,14 @@ current locale:
|
|
70
70
|
=end
|
71
71
|
module SimpleI18n
|
72
72
|
|
73
|
+
# FriendlyId::Config.use will invoke this method when present, to allow
|
74
|
+
# loading dependent modules prior to overriding them when necessary.
|
75
|
+
def self.setup(model_class)
|
76
|
+
model_class.friendly_id_config.use :slugged
|
77
|
+
end
|
78
|
+
|
73
79
|
def self.included(model_class)
|
74
|
-
model_class.
|
75
|
-
friendly_id_config.use :slugged
|
80
|
+
model_class.class_eval do
|
76
81
|
friendly_id_config.class.send :include, Configuration
|
77
82
|
include Model
|
78
83
|
end
|
@@ -84,6 +89,11 @@ current locale:
|
|
84
89
|
set_slug(normalize_friendly_id(text))
|
85
90
|
end
|
86
91
|
end
|
92
|
+
|
93
|
+
def slug=(value)
|
94
|
+
super
|
95
|
+
write_attribute friendly_id_config.slug_column, value
|
96
|
+
end
|
87
97
|
end
|
88
98
|
|
89
99
|
module Configuration
|
data/lib/friendly_id/slug.rb
CHANGED
@@ -2,82 +2,23 @@ module FriendlyId
|
|
2
2
|
# The default slug generator offers functionality to check slug strings for
|
3
3
|
# uniqueness and, if necessary, appends a sequence to guarantee it.
|
4
4
|
class SlugGenerator
|
5
|
-
attr_reader :sluggable, :normalized
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
@sluggable = sluggable
|
10
|
-
@normalized = normalized
|
6
|
+
def initialize(scope)
|
7
|
+
@scope = scope
|
11
8
|
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
"#{normalized}#{separator}#{next_in_sequence}"
|
10
|
+
def available?(slug)
|
11
|
+
!@scope.exists?(slug)
|
16
12
|
end
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
conflict? ? self.next : normalized
|
14
|
+
def add(slug)
|
15
|
+
slug
|
21
16
|
end
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
last_in_sequence == 0 ? 2 : last_in_sequence.next
|
27
|
-
end
|
28
|
-
|
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)
|
34
|
-
slug.split("#{normalized}#{separator}").last.to_i
|
18
|
+
def generate(candidates)
|
19
|
+
candidates.each {|c| return add c if available?(c)}
|
20
|
+
nil
|
35
21
|
end
|
36
22
|
|
37
|
-
def column
|
38
|
-
sluggable.connection.quote_column_name friendly_id_config.slug_column
|
39
|
-
end
|
40
|
-
|
41
|
-
def conflict?
|
42
|
-
!! conflict
|
43
|
-
end
|
44
|
-
|
45
|
-
def conflict
|
46
|
-
unless defined? @conflict
|
47
|
-
@conflict = conflicts.first
|
48
|
-
end
|
49
|
-
@conflict
|
50
|
-
end
|
51
|
-
|
52
|
-
def conflicts
|
53
|
-
sluggable_class = friendly_id_config.model_class.base_class
|
54
|
-
|
55
|
-
pkey = sluggable_class.primary_key
|
56
|
-
value = sluggable.send pkey
|
57
|
-
base = "#{column} = ? OR #{column} LIKE ?"
|
58
|
-
# Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
|
59
|
-
base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
|
60
|
-
scope = sluggable_class.unscoped.where(base, normalized, wildcard)
|
61
|
-
scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?
|
62
|
-
|
63
|
-
length_command = "LENGTH"
|
64
|
-
length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
|
65
|
-
scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
|
66
|
-
end
|
67
|
-
|
68
|
-
def friendly_id_config
|
69
|
-
sluggable.friendly_id_config
|
70
|
-
end
|
71
|
-
|
72
|
-
def separator
|
73
|
-
friendly_id_config.sequence_separator
|
74
|
-
end
|
75
|
-
|
76
|
-
def wildcard
|
77
|
-
# Underscores (matching a single character) and percent signs (matching
|
78
|
-
# any number of characters) need to be escaped
|
79
|
-
# (While this seems like an excessive number of backslashes, it is correct)
|
80
|
-
"#{normalized}#{separator}".gsub(/[_%]/, '\\\\\&') + '%'
|
81
|
-
end
|
82
23
|
end
|
83
24
|
end
|
data/lib/friendly_id/slugged.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require "friendly_id/slug_generator"
|
3
|
+
require "friendly_id/candidates"
|
3
4
|
|
4
5
|
module FriendlyId
|
5
6
|
=begin
|
@@ -82,11 +83,11 @@ FriendlyId will append a sequence to the generated slug to ensure uniqueness:
|
|
82
83
|
car2 = Car.create :title => "Peugot 206"
|
83
84
|
|
84
85
|
car.friendly_id #=> "peugot-206"
|
85
|
-
car2.friendly_id #=> "peugot-206
|
86
|
+
car2.friendly_id #=> "peugot-206-f9f3789a-daec-4156-af1d-fab81aa16ee5"
|
86
87
|
|
87
88
|
==== Sequence Separator - The Two Dashes
|
88
89
|
|
89
|
-
By default, FriendlyId uses
|
90
|
+
By default, FriendlyId uses a dash to separate the slug from a sequence.
|
90
91
|
|
91
92
|
You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
|
92
93
|
sequence_separator} configuration option.
|
@@ -185,11 +186,11 @@ second, in concurrent code, either in threads or multiple processes.
|
|
185
186
|
|
186
187
|
To solve the nested attributes issue, I recommend simply avoiding them when
|
187
188
|
creating more than one nested record for a model that uses FriendlyId. See {this
|
188
|
-
Github issue}[https://github.com/
|
189
|
+
Github issue}[https://github.com/FriendlyId/friendly_id/issues/185] for discussion.
|
189
190
|
|
190
191
|
To solve the concurrency issue, I recommend locking the model's table against
|
191
192
|
inserts while when saving the record. See {this Github
|
192
|
-
issue}[https://github.com/
|
193
|
+
issue}[https://github.com/FriendlyId/friendly_id/issues/180] for discussion.
|
193
194
|
|
194
195
|
=end
|
195
196
|
module Slugged
|
@@ -199,9 +200,9 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
|
199
200
|
def self.included(model_class)
|
200
201
|
model_class.friendly_id_config.instance_eval do
|
201
202
|
self.class.send :include, Configuration
|
202
|
-
self.slug_generator_class ||=
|
203
|
+
self.slug_generator_class ||= SlugGenerator
|
203
204
|
defaults[:slug_column] ||= 'slug'
|
204
|
-
defaults[:sequence_separator] ||= '
|
205
|
+
defaults[:sequence_separator] ||= '-'
|
205
206
|
end
|
206
207
|
model_class.before_validation :set_slug
|
207
208
|
end
|
@@ -250,34 +251,28 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
|
250
251
|
# You can override this method in your model if, for example, you only want
|
251
252
|
# slugs to be generated once, and then never updated.
|
252
253
|
def should_generate_new_friendly_id?
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
return false if base.nil? && slug_value.nil?
|
259
|
-
# Otherwise, if this is a new record, we're definitely going to try to
|
260
|
-
# create a new slug.
|
261
|
-
return true if new_record?
|
262
|
-
slug_base = normalize_friendly_id(base)
|
263
|
-
separator = Regexp.escape friendly_id_config.sequence_separator
|
264
|
-
# If the slug base (with and without sequence) is different from either the current
|
265
|
-
# friendly id or the slug value, then we'll generate a new friendly_id.
|
266
|
-
compare = (current_friendly_id || slug_value)
|
267
|
-
slug_base != compare && slug_base != compare.try(:sub, /#{separator}[\d]*\z/, '')
|
254
|
+
send(friendly_id_config.slug_column).nil? && !send(friendly_id_config.base).nil?
|
255
|
+
end
|
256
|
+
|
257
|
+
def resolve_friendly_id_conflict(candidates)
|
258
|
+
candidates.first + friendly_id_config.sequence_separator + SecureRandom.uuid
|
268
259
|
end
|
269
260
|
|
270
261
|
# Sets the slug.
|
271
|
-
# FIXME: This method sucks and the logic is pretty dubious.
|
272
262
|
def set_slug(normalized_slug = nil)
|
273
|
-
if
|
274
|
-
normalized_slug
|
275
|
-
|
276
|
-
send "#{friendly_id_config.slug_column}=",
|
263
|
+
if should_generate_new_friendly_id?
|
264
|
+
candidates = FriendlyId::Candidates.new(self, normalized_slug || send(friendly_id_config.base))
|
265
|
+
slug = slug_generator.generate(candidates) || resolve_friendly_id_conflict(candidates)
|
266
|
+
send "#{friendly_id_config.slug_column}=", slug
|
277
267
|
end
|
278
268
|
end
|
279
269
|
private :set_slug
|
280
270
|
|
271
|
+
def slug_generator
|
272
|
+
friendly_id_config.slug_generator_class.new(self.class.base_class.unscoped.friendly)
|
273
|
+
end
|
274
|
+
private :slug_generator
|
275
|
+
|
281
276
|
# This module adds the +:slug_column+, and +:sequence_separator+, and
|
282
277
|
# +:slug_generator_class+ configuration options to
|
283
278
|
# {FriendlyId::Configuration FriendlyId::Configuration}.
|
@@ -293,17 +288,10 @@ issue}[https://github.com/norman/friendly_id/issues/180] for discussion.
|
|
293
288
|
|
294
289
|
# The string used to separate a slug base from a numeric sequence.
|
295
290
|
#
|
296
|
-
# By default, +--+ is used to separate the slug from the sequence.
|
297
|
-
# FriendlyId uses two dashes to distinguish sequences from slugs with
|
298
|
-
# numbers in their name.
|
299
|
-
#
|
300
291
|
# You can change the default separator by setting the
|
301
292
|
# {FriendlyId::Slugged::Configuration#sequence_separator
|
302
293
|
# sequence_separator} configuration option.
|
303
|
-
#
|
304
|
-
# For obvious reasons, you should avoid setting it to "+-+" unless you're
|
305
|
-
# sure you will never want to have a friendly id with a number in it.
|
306
|
-
# @return String The sequence separator string. Defaults to "+--+".
|
294
|
+
# @return String The sequence separator string. Defaults to "+-+".
|
307
295
|
def sequence_separator
|
308
296
|
@sequence_separator or defaults[:sequence_separator]
|
309
297
|
end
|
@@ -3,14 +3,15 @@ require "rails/generators/active_record"
|
|
3
3
|
|
4
4
|
# This generator adds a migration for the {FriendlyId::History
|
5
5
|
# FriendlyId::History} addon.
|
6
|
-
class FriendlyIdGenerator <
|
7
|
-
|
8
|
-
|
6
|
+
class FriendlyIdGenerator < ActiveRecord::Generators::Base
|
7
|
+
# ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase which requires a NAME parameter for the
|
8
|
+
# new table name. Our generator always uses 'friendly_id_slugs', so we just set a random name here.
|
9
|
+
argument :name, type: :string, default: 'random_name'
|
9
10
|
|
10
11
|
source_root File.expand_path('../../friendly_id', __FILE__)
|
11
12
|
|
12
13
|
# Copies the migration template to db/migrate.
|
13
|
-
def copy_files
|
14
|
+
def copy_files
|
14
15
|
migration_template 'migration.rb', 'db/migrate/create_friendly_id_slugs.rb'
|
15
16
|
end
|
16
17
|
|
data/test/helper.rb
CHANGED
data/test/history_test.rb
CHANGED
@@ -2,7 +2,7 @@ require "helper"
|
|
2
2
|
|
3
3
|
class Manual < ActiveRecord::Base
|
4
4
|
extend FriendlyId
|
5
|
-
friendly_id :name, :use => :history
|
5
|
+
friendly_id :name, :use => [:slugged, :history]
|
6
6
|
end
|
7
7
|
|
8
8
|
class HistoryTest < MiniTest::Unit::TestCase
|
@@ -29,6 +29,7 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
29
29
|
test "should create new slug record when friendly_id changes" do
|
30
30
|
with_instance_of(model_class) do |record|
|
31
31
|
record.name = record.name + "b"
|
32
|
+
record.slug = nil
|
32
33
|
record.save!
|
33
34
|
assert_equal 2, FriendlyId::Slug.count
|
34
35
|
end
|
@@ -38,10 +39,11 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
38
39
|
with_instance_of(model_class) do |record|
|
39
40
|
old_friendly_id = record.friendly_id
|
40
41
|
record.name = record.name + "b"
|
42
|
+
record.slug = nil
|
41
43
|
record.save!
|
42
44
|
begin
|
43
|
-
assert model_class.find(old_friendly_id)
|
44
|
-
assert model_class.exists?(old_friendly_id), "should exist? by old id"
|
45
|
+
assert model_class.friendly.find(old_friendly_id)
|
46
|
+
assert model_class.friendly.exists?(old_friendly_id), "should exist? by old id"
|
45
47
|
rescue ActiveRecord::RecordNotFound
|
46
48
|
flunk "Could not find record by old id"
|
47
49
|
end
|
@@ -52,8 +54,9 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
52
54
|
transaction do
|
53
55
|
record = model_class.create! :name => "hello"
|
54
56
|
assert_equal 1, FriendlyId::Slug.count
|
55
|
-
record = model_class.find("hello")
|
57
|
+
record = model_class.friendly.find("hello")
|
56
58
|
record.name = "hello again"
|
59
|
+
record.slug = nil
|
57
60
|
record.save!
|
58
61
|
assert_equal 2, FriendlyId::Slug.count
|
59
62
|
end
|
@@ -64,17 +67,7 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
64
67
|
old_friendly_id = record.friendly_id
|
65
68
|
record.name = record.name + "b"
|
66
69
|
record.save!
|
67
|
-
assert !model_class.find(old_friendly_id).readonly?
|
68
|
-
end
|
69
|
-
end
|
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
|
70
|
+
assert !model_class.friendly.find(old_friendly_id).readonly?
|
78
71
|
end
|
79
72
|
end
|
80
73
|
|
@@ -96,16 +89,7 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
96
89
|
first_record.save!
|
97
90
|
second_record = model_class.create! :name => "foo"
|
98
91
|
assert second_record.slug != "foo"
|
99
|
-
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
test 'should increment the sequence by one for each historic slug' do
|
104
|
-
transaction do
|
105
|
-
previous_record = model_class.create! :name => "foo"
|
106
|
-
first_record = model_class.create! :name => 'another'
|
107
|
-
second_record = model_class.create! :name => 'another'
|
108
|
-
assert second_record.slug == "another--2"
|
92
|
+
assert_match(/foo-.+/, second_record.slug)
|
109
93
|
end
|
110
94
|
end
|
111
95
|
|
@@ -114,13 +98,28 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
114
98
|
first_record = model_class.create! :name => "foo"
|
115
99
|
second_record = model_class.create! :name => 'another'
|
116
100
|
|
117
|
-
second_record.update_attributes :name => 'foo'
|
118
|
-
|
119
|
-
|
120
|
-
|
101
|
+
second_record.update_attributes :name => 'foo', :slug => nil
|
102
|
+
assert_match(/foo-.*/, second_record.slug)
|
103
|
+
|
104
|
+
first_record.update_attributes :name => 'another', :slug => nil
|
105
|
+
assert_match(/another-.*/, first_record.slug)
|
121
106
|
end
|
122
107
|
end
|
123
108
|
|
109
|
+
test 'should name table according to prefix and suffix' do
|
110
|
+
transaction do
|
111
|
+
begin
|
112
|
+
prefix = "prefix_"
|
113
|
+
without_prefix = FriendlyId::Slug.table_name
|
114
|
+
ActiveRecord::Base.table_name_prefix = prefix
|
115
|
+
FriendlyId::Slug.reset_table_name
|
116
|
+
assert_equal prefix + without_prefix, FriendlyId::Slug.table_name
|
117
|
+
ensure
|
118
|
+
ActiveRecord::Base.table_name_prefix = ""
|
119
|
+
FriendlyId::Slug.table_name = without_prefix
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
124
123
|
end
|
125
124
|
|
126
125
|
class HistoryTestWithSti < HistoryTest
|
@@ -137,8 +136,6 @@ class HistoryTestWithSti < HistoryTest
|
|
137
136
|
end
|
138
137
|
end
|
139
138
|
|
140
|
-
|
141
|
-
|
142
139
|
class City < ActiveRecord::Base
|
143
140
|
has_many :restaurants
|
144
141
|
end
|
@@ -158,45 +155,55 @@ class ScopedHistoryTest < MiniTest::Unit::TestCase
|
|
158
155
|
end
|
159
156
|
|
160
157
|
test "should find old scoped slugs" do
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
transaction do
|
159
|
+
city = City.create!
|
160
|
+
with_instance_of(Restaurant) do |record|
|
161
|
+
record.city = city
|
164
162
|
|
165
|
-
|
166
|
-
|
163
|
+
record.name = "x"
|
164
|
+
record.slug = nil
|
165
|
+
record.save!
|
167
166
|
|
168
|
-
|
169
|
-
|
167
|
+
record.name = "y"
|
168
|
+
record.slug = nil
|
169
|
+
record.save!
|
170
170
|
|
171
|
-
|
171
|
+
assert_equal city.restaurants.friendly.find("x"), city.restaurants.friendly.find("y")
|
172
|
+
end
|
172
173
|
end
|
173
174
|
end
|
174
175
|
|
175
176
|
test "should consider old scoped slugs when creating slugs" do
|
176
|
-
|
177
|
-
|
178
|
-
|
177
|
+
transaction do
|
178
|
+
city = City.create!
|
179
|
+
with_instance_of(Restaurant) do |record|
|
180
|
+
record.city = city
|
179
181
|
|
180
|
-
|
181
|
-
|
182
|
+
record.name = "x"
|
183
|
+
record.slug = nil
|
184
|
+
record.save!
|
182
185
|
|
183
|
-
|
184
|
-
|
186
|
+
record.name = "y"
|
187
|
+
record.slug = nil
|
188
|
+
record.save!
|
185
189
|
|
186
|
-
|
187
|
-
|
190
|
+
second_record = model_class.create! :city => city, :name => 'x'
|
191
|
+
assert_match(/x-.+/, second_record.friendly_id)
|
188
192
|
|
189
|
-
|
190
|
-
|
193
|
+
third_record = model_class.create! :city => city, :name => 'y'
|
194
|
+
assert_match(/y-.+/, third_record.friendly_id)
|
195
|
+
end
|
191
196
|
end
|
192
197
|
end
|
193
198
|
|
194
199
|
test "should allow equal slugs in different scopes" do
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
200
|
+
transaction do
|
201
|
+
city = City.create!
|
202
|
+
second_city = City.create!
|
203
|
+
record = model_class.create! :city => city, :name => 'x'
|
204
|
+
second_record = model_class.create! :city => second_city, :name => 'x'
|
199
205
|
|
200
|
-
|
206
|
+
assert_equal record.slug, second_record.slug
|
207
|
+
end
|
201
208
|
end
|
202
|
-
end
|
209
|
+
end
|