friendly_id 5.0.0.rc1 → 5.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +7 -37
- data/Changelog.md +11 -0
- data/README.md +14 -2
- data/bench.rb +4 -0
- data/lib/friendly_id/base.rb +4 -0
- data/lib/friendly_id/finder_methods.rb +17 -6
- data/lib/friendly_id/history.rb +17 -4
- data/lib/friendly_id/initializer.rb +5 -1
- data/lib/friendly_id/migration.rb +1 -6
- data/lib/friendly_id/slug_generator.rb +1 -1
- data/lib/friendly_id/slugged.rb +10 -3
- data/lib/friendly_id/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/history_test.rb +37 -0
- data/test/schema.rb +14 -1
- data/test/slugged_test.rb +41 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b8e4a5f27bb5857aa5761f04a7300d96df81078
|
4
|
+
data.tar.gz: 85674fed68afdc2ba0122cff6edb0424eeb3dcaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39e09066a6e62b4243e19f53c8640e15bfb05ed0c70fd4dbbfdfaf3cbb8aab162392e875ec79545e460ecdb716d2f8fd007c97a356963ccde9852b273216828c
|
7
|
+
data.tar.gz: c1b52cbedd571df3644ce78a94b22df6a747b9b22d5d40bede443dcedd9f69c7ad6771f18e3183b1761f6ace0d512590767c349bafe75f9d5c209c537777cd7c
|
data/CONTRIBUTING.md
CHANGED
@@ -1,41 +1,11 @@
|
|
1
1
|
# FriendlyId
|
2
2
|
|
3
|
-
|
3
|
+
Please ask questions on [Stack
|
4
|
+
Overflow](http://stackoverflow.com/questions/tagged/friendly-id) using the
|
5
|
+
"friendly_id" or "friendly-id" tag. Prior to asking, search and see if your
|
6
|
+
question has already been anwered.
|
4
7
|
|
5
|
-
|
6
|
-
question has been answered before.
|
7
|
-
|
8
|
-
Please also take a look at the extensive documentation, especially the Guide.
|
9
|
-
|
10
|
-
## FAQ
|
11
|
-
|
12
|
-
Here are the answers to some common questions.
|
13
|
-
|
14
|
-
### I'm using Rails 4...
|
15
|
-
|
16
|
-
Then you **must** use FriendlyId 5, which is currently available in the master
|
17
|
-
branch of the repository, and will be released to Rubygems soon.
|
18
|
-
|
19
|
-
### I'm seeing a bunch of odd characters at the end of the id
|
20
|
-
|
21
|
-
For example: `2bc08962-b3dd-4f29-b2e6-244710c86106`. FriendlyId 5 uses a UUID
|
22
|
-
rather than a sequence to simplify slug generation and avoid concurrency problems.
|
23
|
-
|
24
|
-
### How do I set up my routes for FriendlyId?
|
25
|
-
|
26
|
-
Exactly like in any other Rails app. FriendlyId doesn't change any routing
|
27
|
-
functionality, it only changes the ids that are shown in your routes.
|
28
|
-
|
29
|
-
TIP: Make your app work the way you want to with numeric id's. Then add
|
30
|
-
FriendlyId later.
|
31
|
-
|
32
|
-
### I'm seeing `undefined method `friendly_id'`
|
33
|
-
|
34
|
-
You probably just added the friendly_id gem. Run `bundle` and restart your
|
35
|
-
Rails app.
|
36
|
-
|
37
|
-
### I'm seeing `uninitialized constant FriendlyId`
|
38
|
-
|
39
|
-
You probably just added the friendly_id gem. Run `bundle` and restart your
|
40
|
-
Rails app.
|
8
|
+
Please only post issues in Github issues for actual bugs.
|
41
9
|
|
10
|
+
I am asking people to do this because the same questions keep getting asked
|
11
|
+
over and over and over again in the issues.
|
data/Changelog.md
CHANGED
@@ -3,6 +3,17 @@
|
|
3
3
|
We would like to think our many {file:Contributors contributors} for
|
4
4
|
suggestions, ideas and improvements to FriendlyId.
|
5
5
|
|
6
|
+
## 5.0.0.rc2 (2013-09-29)
|
7
|
+
|
8
|
+
* When the :finders addon has been included, use it in FriendlyId's internal
|
9
|
+
finds to boost performance.
|
10
|
+
* Use instance methods rather than class methods in migrations.
|
11
|
+
* On find, fall back to super when the primary key is a character type. Thanks
|
12
|
+
to [Jamie Davidson](https://github.com/jhdavids8).
|
13
|
+
* Fix reversion to previously used slug from history table when
|
14
|
+
`should_generate_new_friendly_id?` is overridden.
|
15
|
+
* Fix sequencing of numeric slugs
|
16
|
+
|
6
17
|
## 5.0.0.rc1 (2013-08-28)
|
7
18
|
|
8
19
|
* Removed some outdated tests.
|
data/README.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
[![Build Status](https://travis-ci.org/norman/friendly_id.png)](https://travis-ci.org/norman/friendly_id)
|
2
2
|
|
3
|
+
**GETTING HELP**
|
4
|
+
|
5
|
+
Please ask questions on [Stack
|
6
|
+
Overflow](http://stackoverflow.com/questions/tagged/friendly-id) using the
|
7
|
+
"friendly_id" or "friendly-id" tag. Prior to asking, search and see if your
|
8
|
+
question has already been anwered.
|
9
|
+
|
10
|
+
Please only post issues in Github issues for actual bugs.
|
11
|
+
|
12
|
+
I am asking people to do this because the same questions keep getting asked
|
13
|
+
over and over and over again in the issues.
|
14
|
+
|
3
15
|
**VERSION NOTE**
|
4
16
|
|
5
17
|
**Rails 4**:
|
@@ -115,7 +127,7 @@ generated in `config/initializers/friendly_id.rb`. This file contains notes
|
|
115
127
|
describing how to restore (or not) some of the defaults from FriendlyId 4.0.
|
116
128
|
|
117
129
|
If you want to use the `:history` and `:scoped` addons together, you must add a
|
118
|
-
`:scope` column to your
|
130
|
+
`:scope` column to your friendly_id_slugs table and replace the unique index on
|
119
131
|
`:slug` and `:sluggable_type` with a unique index on those two columns, plus
|
120
132
|
the new `:scope` column.
|
121
133
|
|
@@ -151,7 +163,7 @@ cd my_app
|
|
151
163
|
```
|
152
164
|
```ruby
|
153
165
|
# Gemfile
|
154
|
-
gem 'friendly_id', '5.0.0.
|
166
|
+
gem 'friendly_id', '5.0.0.rc2' # Note: You MUST use 5.0.0 or greater for Rails 4.0+
|
155
167
|
```
|
156
168
|
```shell
|
157
169
|
rails generate friendly_id
|
data/bench.rb
CHANGED
@@ -72,6 +72,10 @@ Benchmark.bmbm do |x|
|
|
72
72
|
N.times {transaction {Journalist.create :name => Faker::Name.name}}
|
73
73
|
end
|
74
74
|
|
75
|
+
x.report 'insert (in-table-slug; included FinderMethods)' do
|
76
|
+
N.times {transaction {Restaurant.create :name => Faker::Name.name}}
|
77
|
+
end
|
78
|
+
|
75
79
|
x.report 'insert (external slug)' do
|
76
80
|
N.times {transaction {Manual.create :name => Faker::Name.name}}
|
77
81
|
end
|
data/lib/friendly_id/base.rb
CHANGED
@@ -216,6 +216,10 @@ often better and easier to use {FriendlyId::Slugged slugs}.
|
|
216
216
|
config.model_class = self
|
217
217
|
end
|
218
218
|
end
|
219
|
+
|
220
|
+
def primary_key_type
|
221
|
+
@primary_key_type ||= columns.find(&:primary).type
|
222
|
+
end
|
219
223
|
end
|
220
224
|
|
221
225
|
# Instance methods that will be added to all classes using FriendlyId.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module FriendlyId
|
2
2
|
|
3
3
|
module FinderMethods
|
4
|
-
|
4
|
+
|
5
5
|
# Finds a record using the given id.
|
6
6
|
#
|
7
7
|
# If the id is "unfriendly", it will call the original find method.
|
@@ -19,7 +19,7 @@ module FriendlyId
|
|
19
19
|
id = args.first
|
20
20
|
return super if args.count != 1 || id.unfriendly_id?
|
21
21
|
first_by_friendly_id(id).tap {|result| return result unless result.nil?}
|
22
|
-
return super if
|
22
|
+
return super if potential_primary_key?(id)
|
23
23
|
raise ActiveRecord::RecordNotFound
|
24
24
|
end
|
25
25
|
|
@@ -36,14 +36,25 @@ module FriendlyId
|
|
36
36
|
first_by_friendly_id(id) or raise ActiveRecord::RecordNotFound
|
37
37
|
end
|
38
38
|
|
39
|
+
def exists_by_friendly_id?(id)
|
40
|
+
where(friendly_id_config.query_field => id).exists?
|
41
|
+
end
|
42
|
+
|
39
43
|
private
|
40
44
|
|
41
|
-
def
|
42
|
-
|
45
|
+
def potential_primary_key?(id)
|
46
|
+
case primary_key_type
|
47
|
+
when :integer
|
48
|
+
Integer(id, 10) rescue false
|
49
|
+
when :uuid
|
50
|
+
id.match /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
43
54
|
end
|
44
55
|
|
45
|
-
def
|
46
|
-
where(friendly_id_config.query_field => id).
|
56
|
+
def first_by_friendly_id(id)
|
57
|
+
where(friendly_id_config.query_field => id).first
|
47
58
|
end
|
48
59
|
|
49
60
|
end
|
data/lib/friendly_id/history.rb
CHANGED
@@ -82,16 +82,16 @@ method.
|
|
82
82
|
module HistoryFinderMethods
|
83
83
|
include FinderMethods
|
84
84
|
|
85
|
+
def exists_by_friendly_id?(id)
|
86
|
+
joins(:slugs).where(arel_table[friendly_id_config.query_field].eq(id).or(slug_history_clause(id))).exists?
|
87
|
+
end
|
88
|
+
|
85
89
|
private
|
86
90
|
|
87
91
|
def first_by_friendly_id(id)
|
88
92
|
joins(:slugs).where(slug_history_clause(id)).readonly(false).first
|
89
93
|
end
|
90
94
|
|
91
|
-
def exists_by_friendly_id?(id)
|
92
|
-
joins(:slugs).where(arel_table[friendly_id_config.query_field].eq(id).or(slug_history_clause(id))).exists?
|
93
|
-
end
|
94
|
-
|
95
95
|
def slug_history_clause(id)
|
96
96
|
Slug.arel_table[:sluggable_type].eq(base_class.to_s).and(Slug.arel_table[:slug].eq(id))
|
97
97
|
end
|
@@ -99,6 +99,19 @@ method.
|
|
99
99
|
|
100
100
|
private
|
101
101
|
|
102
|
+
# If we're updating, don't consider historic slugs for the same record
|
103
|
+
# to be conflicts. This will allow a record to revert to a previously
|
104
|
+
# used slug.
|
105
|
+
def scope_for_slug_generator
|
106
|
+
relation = super
|
107
|
+
return relation if new_record?
|
108
|
+
relation = relation.merge(Slug.where('sluggable_id <> ?', id))
|
109
|
+
if friendly_id_config.uses?(:scoped)
|
110
|
+
relation = relation.where(:scope => serialized_scope)
|
111
|
+
end
|
112
|
+
relation
|
113
|
+
end
|
114
|
+
|
102
115
|
def create_slug
|
103
116
|
return unless friendly_id
|
104
117
|
return if slugs.first.try(:slug) == friendly_id
|
@@ -34,6 +34,10 @@ FriendlyId.defaults do |config|
|
|
34
34
|
# all applications, so you must explicity opt-in to this behavior. You can
|
35
35
|
# always also configure it on a per-model basis if you prefer.
|
36
36
|
#
|
37
|
+
# Something else to consider is that using the :finders addon boosts
|
38
|
+
# performance because it will avoid Rails-internal code that makes runtime
|
39
|
+
# calls to `Module.extend`.
|
40
|
+
#
|
37
41
|
# config.use :finders
|
38
42
|
#
|
39
43
|
# ## Slugs
|
@@ -81,4 +85,4 @@ FriendlyId.defaults do |config|
|
|
81
85
|
# text.to_slug.normalize! :transliterations => [:russian, :latin]
|
82
86
|
# end
|
83
87
|
# }
|
84
|
-
end
|
88
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
class CreateFriendlyIdSlugs < ActiveRecord::Migration
|
2
|
-
|
3
|
-
def self.up
|
2
|
+
def change
|
4
3
|
create_table :friendly_id_slugs do |t|
|
5
4
|
t.string :slug, :null => false
|
6
5
|
t.integer :sluggable_id, :null => false
|
@@ -13,8 +12,4 @@ class CreateFriendlyIdSlugs < ActiveRecord::Migration
|
|
13
12
|
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
|
14
13
|
add_index :friendly_id_slugs, :sluggable_type
|
15
14
|
end
|
16
|
-
|
17
|
-
def self.down
|
18
|
-
drop_table :friendly_id_slugs
|
19
|
-
end
|
20
15
|
end
|
data/lib/friendly_id/slugged.rb
CHANGED
@@ -297,13 +297,20 @@ Github issue](https://github.com/norman/friendly_id/issues/185) for discussion.
|
|
297
297
|
end
|
298
298
|
private :set_slug
|
299
299
|
|
300
|
-
def
|
301
|
-
scope = self.class.base_class.unscoped
|
300
|
+
def scope_for_slug_generator
|
301
|
+
scope = self.class.base_class.unscoped
|
302
|
+
scope = scope.friendly unless friendly_id_config.uses? :finders
|
303
|
+
|
302
304
|
if changed.include?(friendly_id_config.slug_column)
|
303
305
|
column = self.class.quoted_table_name + '.' + self.class.quoted_primary_key
|
304
306
|
scope = scope.where("#{column} <> ?", send(self.class.primary_key))
|
305
307
|
end
|
306
|
-
|
308
|
+
scope
|
309
|
+
end
|
310
|
+
private :scope_for_slug_generator
|
311
|
+
|
312
|
+
def slug_generator
|
313
|
+
friendly_id_config.slug_generator_class.new(scope_for_slug_generator)
|
307
314
|
end
|
308
315
|
private :slug_generator
|
309
316
|
|
data/lib/friendly_id/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/history_test.rb
CHANGED
@@ -62,6 +62,14 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
test "should not be read only when found by slug" do
|
66
|
+
with_instance_of(model_class) do |record|
|
67
|
+
refute model_class.friendly.find(record.friendly_id).readonly?
|
68
|
+
assert record.update_attribute :name, 'foo'
|
69
|
+
assert record.update_attributes name: 'foo'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
65
73
|
test "should not be read only when found by old slug" do
|
66
74
|
with_instance_of(model_class) do |record|
|
67
75
|
old_friendly_id = record.friendly_id
|
@@ -74,10 +82,13 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
74
82
|
test "should handle renames" do
|
75
83
|
with_instance_of(model_class) do |record|
|
76
84
|
record.name = 'x'
|
85
|
+
record.slug = nil
|
77
86
|
assert record.save
|
78
87
|
record.name = 'y'
|
88
|
+
record.slug = nil
|
79
89
|
assert record.save
|
80
90
|
record.name = 'x'
|
91
|
+
record.slug = nil
|
81
92
|
assert record.save
|
82
93
|
end
|
83
94
|
end
|
@@ -122,6 +133,32 @@ class HistoryTest < MiniTest::Unit::TestCase
|
|
122
133
|
end
|
123
134
|
end
|
124
135
|
|
136
|
+
class HistoryTestWithAutomaticSlugRegeneration < HistoryTest
|
137
|
+
class Manual < ActiveRecord::Base
|
138
|
+
extend FriendlyId
|
139
|
+
friendly_id :name, :use => [:slugged, :history]
|
140
|
+
|
141
|
+
def should_generate_new_friendly_id?
|
142
|
+
slug.blank? or name_changed?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def model_class
|
147
|
+
Manual
|
148
|
+
end
|
149
|
+
|
150
|
+
test 'should allow reversion back to a previously used slug' do
|
151
|
+
with_instance_of(model_class, name: 'foo') do |record|
|
152
|
+
record.name = 'bar'
|
153
|
+
record.save!
|
154
|
+
assert_equal 'bar', record.friendly_id
|
155
|
+
record.name = 'foo'
|
156
|
+
record.save!
|
157
|
+
assert_equal 'foo', record.friendly_id
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
125
162
|
class HistoryTestWithSti < HistoryTest
|
126
163
|
class Journalist < ActiveRecord::Base
|
127
164
|
extend FriendlyId
|
data/test/schema.rb
CHANGED
@@ -14,7 +14,7 @@ module FriendlyId
|
|
14
14
|
def up
|
15
15
|
# TODO: use schema version to avoid ugly hacks like this
|
16
16
|
return if @done
|
17
|
-
CreateFriendlyIdSlugs.up
|
17
|
+
CreateFriendlyIdSlugs.migrate :up
|
18
18
|
|
19
19
|
tables.each do |table_name|
|
20
20
|
create_table table_name do |t|
|
@@ -23,6 +23,15 @@ module FriendlyId
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
tables_with_string_primary_key.each do |table_name|
|
27
|
+
create_table table_name, primary_key: :string_key, id: false do |t|
|
28
|
+
t.string :name
|
29
|
+
t.string :string_key, null: false
|
30
|
+
t.string :slug
|
31
|
+
end
|
32
|
+
add_index table_name, :slug, unique: true
|
33
|
+
end
|
34
|
+
|
26
35
|
slugged_tables.each do |table_name|
|
27
36
|
add_column table_name, :slug, :string
|
28
37
|
add_index table_name, :slug, :unique => true
|
@@ -64,6 +73,10 @@ module FriendlyId
|
|
64
73
|
%w[journalists articles novelists novels manuals]
|
65
74
|
end
|
66
75
|
|
76
|
+
def tables_with_string_primary_key
|
77
|
+
["menu_items"]
|
78
|
+
end
|
79
|
+
|
67
80
|
def scoped_tables
|
68
81
|
["restaurants"]
|
69
82
|
end
|
data/test/slugged_test.rb
CHANGED
@@ -134,6 +134,13 @@ class SlugGeneratorTest < MiniTest::Unit::TestCase
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
+
test "should correctly sequence numeric slugs" do
|
138
|
+
transaction do
|
139
|
+
n2 = 2.times.map {Article.create :name => '123'}.last
|
140
|
+
assert_match(/\A123-.*/, n2.friendly_id)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
137
144
|
end
|
138
145
|
|
139
146
|
class SlugSeparatorTest < MiniTest::Unit::TestCase
|
@@ -205,6 +212,40 @@ class DefaultScopeTest < MiniTest::Unit::TestCase
|
|
205
212
|
end
|
206
213
|
end
|
207
214
|
|
215
|
+
class StringAsPrimaryKeyFindTest < MiniTest::Unit::TestCase
|
216
|
+
include FriendlyId::Test
|
217
|
+
|
218
|
+
class MenuItem < ActiveRecord::Base
|
219
|
+
extend FriendlyId
|
220
|
+
friendly_id :name, :use => :slugged
|
221
|
+
before_create :init_primary_key
|
222
|
+
|
223
|
+
def self.primary_key
|
224
|
+
"string_key"
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
def init_primary_key
|
229
|
+
self.string_key = SecureRandom.uuid
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def model_class
|
234
|
+
MenuItem
|
235
|
+
end
|
236
|
+
|
237
|
+
test "should have a string as a primary key" do
|
238
|
+
assert_equal model_class.primary_key, "string_key"
|
239
|
+
assert_equal model_class.columns.find(&:primary).name, "string_key"
|
240
|
+
end
|
241
|
+
|
242
|
+
test "should be findable by the string primary key" do
|
243
|
+
with_instance_of(model_class) do |record|
|
244
|
+
assert model_class.friendly.find record.id
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
208
249
|
class UnderscoreAsSequenceSeparatorRegressionTest < MiniTest::Unit::TestCase
|
209
250
|
include FriendlyId::Test
|
210
251
|
|
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: 5.0.0.
|
4
|
+
version: 5.0.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Norman Clarke
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|