friendly_id 5.0.0.beta4 → 5.0.0.rc1
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 +4 -4
- data/.gitignore +2 -0
- data/.yardopts +1 -0
- data/Changelog.md +7 -0
- data/README.md +11 -4
- data/Rakefile +6 -21
- data/guide.rb +17 -0
- data/lib/friendly_id.rb +13 -0
- data/lib/friendly_id/initializer.rb +1 -1
- data/lib/friendly_id/object_utils.rb +1 -4
- data/lib/friendly_id/reserved.rb +8 -8
- data/lib/friendly_id/simple_i18n.rb +4 -4
- data/lib/friendly_id/version.rb +1 -1
- metadata +3 -8
- data/Guide.md +0 -597
- data/test/compatibility/ancestry/Gemfile +0 -8
- data/test/compatibility/ancestry/ancestry_test.rb +0 -34
- data/test/compatibility/threading/Gemfile +0 -8
- data/test/compatibility/threading/threading.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb59a1c03451e5b3848ab8fa00da5aa476276d79
|
4
|
+
data.tar.gz: f6057d3fad4267d1cea422b0eb72b78f433afc78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bbd5d21be8df888836e6e98b14c43d45eb89da31816830c5287038dce5f1a60c9920adf3b69a498e96b5d0cb6addfe952c5983d307228f5c57726095d9160c7
|
7
|
+
data.tar.gz: cae59c6cbc8406c0cedf700ad1201a23b01d74ec6b7d871333d8c156e273332485f581b59d2df9e59da689e4c4219dffe6a8f770f7a48e9f8af1b8b0a18b8de3
|
data/.gitignore
CHANGED
data/.yardopts
CHANGED
data/Changelog.md
CHANGED
@@ -3,6 +3,13 @@
|
|
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.rc1 (2013-08-28)
|
7
|
+
|
8
|
+
* Removed some outdated tests.
|
9
|
+
* Improved documentation.
|
10
|
+
* Removed Guide from repository and added tasks to maintain docs up to date
|
11
|
+
on Github pages at http://norman.github.io/friendly_id.
|
12
|
+
|
6
13
|
## 5.0.0.beta4 (2013-08-21)
|
7
14
|
|
8
15
|
* Add an initializer to the generator; move the default reserved words there.
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ branch](https://github.com/norman/friendly_id/tree/4.0-stable).
|
|
15
15
|
|
16
16
|
# FriendlyId
|
17
17
|
|
18
|
-
<em>For the most complete, user-friendly documentation, see the [FriendlyId Guide](http://
|
18
|
+
<em>For the most complete, user-friendly documentation, see the [FriendlyId Guide](http://norman.github.io/friendly_id/file.Guide.html).</em>
|
19
19
|
|
20
20
|
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
|
21
21
|
Active Record. It lets you create pretty URLs and work with human-friendly
|
@@ -91,11 +91,13 @@ The most important changes are:
|
|
91
91
|
restaurant.friendly_id # the-plaza-diner
|
92
92
|
|
93
93
|
You can restore some of the old behavior by overriding the
|
94
|
-
`should_generate_new_friendly_id
|
94
|
+
`should_generate_new_friendly_id?` method.
|
95
95
|
|
96
96
|
* The `friendly_id` Rails generator now generates an initializer showing you
|
97
97
|
how to do some commmon global configuration.
|
98
98
|
|
99
|
+
* The Globalize plugin has moved to a separate gem (currently in alpha).
|
100
|
+
|
99
101
|
* The `:reserved` module no longer includes any default reserved words.
|
100
102
|
Previously it blocked "edit" and "new" everywhere. The default word list has
|
101
103
|
been moved to `config/initializers/friendly_id.rb` and now includes many more
|
@@ -127,10 +129,15 @@ A migration like this should be sufficient:
|
|
127
129
|
## Docs
|
128
130
|
|
129
131
|
The most current docs from the master branch can always be found
|
130
|
-
[here](http://
|
132
|
+
[here](http://norman.github.io/friendly_id).
|
133
|
+
|
134
|
+
Docs for older versions are also available:
|
135
|
+
|
136
|
+
* [4.0](http://norman.github.io/friendly_id/4.0/)
|
137
|
+
* [3.3](http://norman.github.io/friendly_id/3.3/)
|
131
138
|
|
132
139
|
The best place to start is with the
|
133
|
-
[Guide](http://
|
140
|
+
[Guide](http://norman.github.io/friendly_id/file.Guide.html),
|
134
141
|
which compiles the top-level RDocs into one outlined document.
|
135
142
|
|
136
143
|
You might also want to watch Ryan Bates's [Railscast on FriendlyId](http://railscasts.com/episodes/314-pretty-urls-with-friendlyid),
|
data/Rakefile
CHANGED
@@ -27,7 +27,7 @@ task :gem do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
desc "Build YARD documentation"
|
30
|
-
task :yard
|
30
|
+
task :yard do
|
31
31
|
puts %x{bundle exec yard}
|
32
32
|
end
|
33
33
|
|
@@ -38,26 +38,7 @@ end
|
|
38
38
|
|
39
39
|
desc "Generate Guide.md"
|
40
40
|
task :guide do
|
41
|
-
|
42
|
-
path = File.expand_path("../#{path}", __FILE__)
|
43
|
-
match = File.read(path).match(/\n=begin(.*)\n=end/m)[1].to_s
|
44
|
-
match.split("\n").reject {|x| x =~ /^@/}.join("\n")
|
45
|
-
end
|
46
|
-
|
47
|
-
buffer = []
|
48
|
-
|
49
|
-
buffer << read_comments("lib/friendly_id.rb")
|
50
|
-
buffer << read_comments("lib/friendly_id/base.rb")
|
51
|
-
buffer << read_comments("lib/friendly_id/finders.rb")
|
52
|
-
buffer << read_comments("lib/friendly_id/slugged.rb")
|
53
|
-
buffer << read_comments("lib/friendly_id/history.rb")
|
54
|
-
buffer << read_comments("lib/friendly_id/scoped.rb")
|
55
|
-
buffer << read_comments("lib/friendly_id/simple_i18n.rb")
|
56
|
-
buffer << read_comments("lib/friendly_id/reserved.rb")
|
57
|
-
|
58
|
-
File.open("Guide.md", "w") do |file|
|
59
|
-
file.write(buffer.join("\n"))
|
60
|
-
end
|
41
|
+
load File.expand_path('../guide.rb', __FILE__)
|
61
42
|
end
|
62
43
|
|
63
44
|
namespace :test do
|
@@ -110,3 +91,7 @@ namespace :db do
|
|
110
91
|
end
|
111
92
|
|
112
93
|
task :doc => :yard
|
94
|
+
|
95
|
+
task :docs do
|
96
|
+
sh %{git checkout gh-pages && rake doc && git checkout @{-1}}
|
97
|
+
end
|
data/guide.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This script generates the Guide.md file included in the Yard docs.
|
4
|
+
|
5
|
+
def comments_from path
|
6
|
+
path = File.expand_path("../lib/friendly_id/#{path}", __FILE__)
|
7
|
+
match = File.read(path).match(/\n=begin(.*)\n=end/m)[1].to_s
|
8
|
+
match.split("\n").reject {|x| x =~ /^@/}.join("\n").strip
|
9
|
+
end
|
10
|
+
|
11
|
+
File.open(File.expand_path('../Guide.md', __FILE__), 'w:utf-8') do |guide|
|
12
|
+
['../friendly_id.rb', 'base.rb', 'finders.rb', 'slugged.rb', 'history.rb',
|
13
|
+
'scoped.rb', 'simple_i18n.rb', 'reserved.rb'].each do |file|
|
14
|
+
guide.write comments_from file
|
15
|
+
guide.write "\n"
|
16
|
+
end
|
17
|
+
end
|
data/lib/friendly_id.rb
CHANGED
@@ -50,6 +50,19 @@ module FriendlyId
|
|
50
50
|
autoload :Slugged, "friendly_id/slugged"
|
51
51
|
autoload :Finders, "friendly_id/finders"
|
52
52
|
|
53
|
+
# Instances of these classes will never be considered a friendly id.
|
54
|
+
# @see FriendlyId::ObjectUtils#friendly_id
|
55
|
+
UNFRIENDLY_CLASSES = [
|
56
|
+
ActiveRecord::Base,
|
57
|
+
Array,
|
58
|
+
FalseClass,
|
59
|
+
Hash,
|
60
|
+
NilClass,
|
61
|
+
Numeric,
|
62
|
+
Symbol,
|
63
|
+
TrueClass
|
64
|
+
]
|
65
|
+
|
53
66
|
# FriendlyId takes advantage of `extended` to do basic model setup, primarily
|
54
67
|
# extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
|
55
68
|
# friendly_id} as a class method.
|
@@ -29,10 +29,7 @@ module FriendlyId
|
|
29
29
|
def friendly_id?
|
30
30
|
# Considered unfriendly if this is an instance of an unfriendly class or
|
31
31
|
# one of its descendants.
|
32
|
-
|
33
|
-
Symbol, TrueClass, FalseClass]
|
34
|
-
|
35
|
-
if unfriendly_classes.detect {|klass| self.class <= klass}
|
32
|
+
if FriendlyId::UNFRIENDLY_CLASSES.detect {|klass| self.class <= klass}
|
36
33
|
false
|
37
34
|
elsif respond_to?(:to_i) && to_i.to_s != to_s
|
38
35
|
true
|
data/lib/friendly_id/reserved.rb
CHANGED
@@ -4,7 +4,7 @@ module FriendlyId
|
|
4
4
|
|
5
5
|
## Reserved Words
|
6
6
|
|
7
|
-
The {FriendlyId::Reserved Reserved} module adds the ability to
|
7
|
+
The {FriendlyId::Reserved Reserved} module adds the ability to exclude a list of
|
8
8
|
words from use as FriendlyId slugs.
|
9
9
|
|
10
10
|
With Ruby on Rails, FriendlyId's generator generates an initializer that
|
@@ -17,16 +17,16 @@ it will have no field to highlight. If you'd like to change this so that
|
|
17
17
|
scaffolding works as expected, one way to accomplish this is to move the error
|
18
18
|
message to a different field. For example:
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
class Person < ActiveRecord::Base
|
21
|
+
extend FriendlyId
|
22
|
+
friendly_id :name, use: :slugged
|
23
23
|
|
24
|
-
|
24
|
+
after_validation :move_friendly_id_error_to_name
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
def move_friendly_id_error_to_name
|
27
|
+
errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present?
|
28
|
+
end
|
28
29
|
end
|
29
|
-
end
|
30
30
|
|
31
31
|
=end
|
32
32
|
module Reserved
|
@@ -37,7 +37,7 @@ friendly_id_globalize gem instead.
|
|
37
37
|
Finds will take into consideration the current locale:
|
38
38
|
|
39
39
|
I18n.locale = :es
|
40
|
-
Post.find("la-guerra-de-las-
|
40
|
+
Post.find("la-guerra-de-las-galaxias")
|
41
41
|
I18n.locale = :en
|
42
42
|
Post.find("star-wars")
|
43
43
|
|
@@ -45,7 +45,7 @@ To find a slug by an explicit locale, perform the find inside a block
|
|
45
45
|
passed to I18n's `with_locale` method:
|
46
46
|
|
47
47
|
I18n.with_locale(:es) do
|
48
|
-
Post.find("la-guerra-de-las-
|
48
|
+
Post.find("la-guerra-de-las-galaxias")
|
49
49
|
end
|
50
50
|
|
51
51
|
### Creating Records
|
@@ -59,13 +59,13 @@ To translate an existing record's friendly_id, use
|
|
59
59
|
you add is properly escaped, transliterated and sequenced:
|
60
60
|
|
61
61
|
post = Post.create :name => "Star Wars"
|
62
|
-
post.set_friendly_id("La guerra de las
|
62
|
+
post.set_friendly_id("La guerra de las galaxias", :es)
|
63
63
|
|
64
64
|
If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the
|
65
65
|
current locale:
|
66
66
|
|
67
67
|
I18n.with_locale(:es) do
|
68
|
-
post.set_friendly_id("La guerra de las
|
68
|
+
post.set_friendly_id("La guerra de las galaxias")
|
69
69
|
end
|
70
70
|
=end
|
71
71
|
module SimpleI18n
|
data/lib/friendly_id/version.rb
CHANGED
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.rc1
|
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-08-
|
12
|
+
date: 2013-08-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -155,13 +155,13 @@ files:
|
|
155
155
|
- CONTRIBUTING.md
|
156
156
|
- Changelog.md
|
157
157
|
- Gemfile
|
158
|
-
- Guide.md
|
159
158
|
- MIT-LICENSE
|
160
159
|
- README.md
|
161
160
|
- Rakefile
|
162
161
|
- bench.rb
|
163
162
|
- friendly_id.gemspec
|
164
163
|
- gemfiles/Gemfile.rails-4.0.rb
|
164
|
+
- guide.rb
|
165
165
|
- lib/friendly_id.rb
|
166
166
|
- lib/friendly_id/.gitattributes
|
167
167
|
- lib/friendly_id/base.rb
|
@@ -182,11 +182,6 @@ files:
|
|
182
182
|
- lib/friendly_id/version.rb
|
183
183
|
- lib/generators/friendly_id_generator.rb
|
184
184
|
- test/base_test.rb
|
185
|
-
- test/compatibility/ancestry/Gemfile
|
186
|
-
- test/compatibility/ancestry/ancestry_test.rb
|
187
|
-
- test/compatibility/threading/Gemfile
|
188
|
-
- test/compatibility/threading/Gemfile.lock
|
189
|
-
- test/compatibility/threading/threading.rb
|
190
185
|
- test/configuration_test.rb
|
191
186
|
- test/core_test.rb
|
192
187
|
- test/databases.yml
|
data/Guide.md
DELETED
@@ -1,597 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
## About FriendlyId
|
4
|
-
|
5
|
-
FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
|
6
|
-
in your URLs with strings:
|
7
|
-
|
8
|
-
# without FriendlyId
|
9
|
-
http://example.com/states/4323454
|
10
|
-
|
11
|
-
# with FriendlyId
|
12
|
-
http://example.com/states/washington
|
13
|
-
|
14
|
-
It requires few changes to your application code and offers flexibility,
|
15
|
-
performance and a well-documented codebase.
|
16
|
-
|
17
|
-
### Core Concepts
|
18
|
-
|
19
|
-
#### Slugs
|
20
|
-
|
21
|
-
The concept of *slugs* is at the heart of FriendlyId.
|
22
|
-
|
23
|
-
A slug is the part of a URL which identifies a page using human-readable
|
24
|
-
keywords, rather than an opaque identifier such as a numeric id. This can make
|
25
|
-
your application more friendly both for users and search engine.
|
26
|
-
|
27
|
-
#### Finders: Slugs Act Like Numeric IDs
|
28
|
-
|
29
|
-
To the extent possible, FriendlyId lets you treat text-based identifiers like
|
30
|
-
normal IDs. This means that you can perform finds with slugs just like you do
|
31
|
-
with numeric ids:
|
32
|
-
|
33
|
-
Person.find(82542335)
|
34
|
-
Person.friendly.find("joe")
|
35
|
-
|
36
|
-
|
37
|
-
## Setting Up FriendlyId in Your Model
|
38
|
-
|
39
|
-
To use FriendlyId in your ActiveRecord models, you must first either extend or
|
40
|
-
include the FriendlyId module (it makes no difference), then invoke the
|
41
|
-
{FriendlyId::Base#friendly_id friendly_id} method to configure your desired
|
42
|
-
options:
|
43
|
-
|
44
|
-
class Foo < ActiveRecord::Base
|
45
|
-
include FriendlyId
|
46
|
-
friendly_id :bar, :use => [:slugged, :simple_i18n]
|
47
|
-
end
|
48
|
-
|
49
|
-
The most important option is `:use`, which you use to tell FriendlyId which
|
50
|
-
addons it should use. See the documentation for this method for a list of all
|
51
|
-
available addons, or skim through the rest of the docs to get a high-level
|
52
|
-
overview.
|
53
|
-
|
54
|
-
### The Default Setup: Simple Models
|
55
|
-
|
56
|
-
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
57
|
-
column with no spaces or special characters, and that is seldom or never
|
58
|
-
updated. The most common example of this is a user name:
|
59
|
-
|
60
|
-
class User < ActiveRecord::Base
|
61
|
-
extend FriendlyId
|
62
|
-
friendly_id :login
|
63
|
-
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
64
|
-
end
|
65
|
-
|
66
|
-
@user = User.friendly.find "joe" # the old User.find(1) still works, too
|
67
|
-
@user.to_param # returns "joe"
|
68
|
-
redirect_to @user # the URL will be /users/joe
|
69
|
-
|
70
|
-
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
71
|
-
modify the value of the column, and your application should ensure that the
|
72
|
-
value is unique and admissible in a URL:
|
73
|
-
|
74
|
-
class City < ActiveRecord::Base
|
75
|
-
extend FriendlyId
|
76
|
-
friendly_id :name
|
77
|
-
end
|
78
|
-
|
79
|
-
@city.friendly.find "Viña del Mar"
|
80
|
-
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
81
|
-
|
82
|
-
Writing the code to process an arbitrary string into a good identifier for use
|
83
|
-
in a URL can be repetitive and surprisingly tricky, so for this reason it's
|
84
|
-
often better and easier to use {FriendlyId::Slugged slugs}.
|
85
|
-
|
86
|
-
## Performing Finds with FriendlyId
|
87
|
-
|
88
|
-
FriendlyId offers enhanced finders which will search for your record by
|
89
|
-
friendly id, and fall back to the numeric id if necessary. This makes it easy
|
90
|
-
to add FriendlyId to an existing application with minimal code modification.
|
91
|
-
|
92
|
-
By default, these methods are available only on the `friendly` scope:
|
93
|
-
|
94
|
-
Restaurant.friendly.find('plaza-diner') #=> works
|
95
|
-
Restaurant.friendly.find(23) #=> also works
|
96
|
-
Restaurant.find(23) #=> still works
|
97
|
-
Restaurant.find('plaza-diner') #=> will not work
|
98
|
-
|
99
|
-
### Restoring FriendlyId 4.0-style finders
|
100
|
-
|
101
|
-
Prior to version 5.0, FriendlyId overrode the default finder methods to perform
|
102
|
-
friendly finds all the time. This required modifying parts of Rails that did
|
103
|
-
not have a public API, which was harder to maintain and at times caused
|
104
|
-
compatiblity problems. In 5.0 we decided change the library's defaults and add
|
105
|
-
the friendly finder methods only to the `friendly` scope in order to boost
|
106
|
-
compatiblity. However, you can still opt-in to original functionality very
|
107
|
-
easily by using the `:finders` addon:
|
108
|
-
|
109
|
-
class Restaurant < ActiveRecord::Base
|
110
|
-
extend FriendlyId
|
111
|
-
|
112
|
-
scope :active, -> {where(:active => true)}
|
113
|
-
|
114
|
-
friendly_id :name, :use => [:slugged, :finders]
|
115
|
-
end
|
116
|
-
|
117
|
-
Restaurant.friendly.find('plaza-diner') #=> works
|
118
|
-
Restaurant.find('plaza-diner') #=> now also works
|
119
|
-
Restaurant.active.find('plaza-diner') #=> now also works
|
120
|
-
|
121
|
-
### Updating your application to use FriendlyId's finders
|
122
|
-
|
123
|
-
Unless you've chosen to use the `:finders` addon, be sure to modify the finders
|
124
|
-
in your controllers to use the `friendly` scope. For example:
|
125
|
-
|
126
|
-
# before
|
127
|
-
def set_restaurant
|
128
|
-
@restaurant = Restaurant.find(params[:id])
|
129
|
-
end
|
130
|
-
|
131
|
-
# after
|
132
|
-
def set_restaurant
|
133
|
-
@restaurant = Restaurant.friendly.find(params[:id])
|
134
|
-
end
|
135
|
-
|
136
|
-
|
137
|
-
## Slugged Models
|
138
|
-
|
139
|
-
FriendlyId can use a separate column to store slugs for models which require
|
140
|
-
some text processing.
|
141
|
-
|
142
|
-
For example, blog applications typically use a post title to provide the basis
|
143
|
-
of a search engine friendly URL. Such identifiers typically lack uppercase
|
144
|
-
characters, use ASCII to approximate UTF-8 character, and strip out other
|
145
|
-
characters which may make them aesthetically unappealing or error-prone when
|
146
|
-
used in a URL.
|
147
|
-
|
148
|
-
class Post < ActiveRecord::Base
|
149
|
-
extend FriendlyId
|
150
|
-
friendly_id :title, :use => :slugged
|
151
|
-
end
|
152
|
-
|
153
|
-
@post = Post.create(:title => "This is the first post!")
|
154
|
-
@post.friendly_id # returns "this-is-the-first-post"
|
155
|
-
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
156
|
-
|
157
|
-
In general, use slugs by default unless you know for sure you don't need them.
|
158
|
-
To activate the slugging functionality, use the {FriendlyId::Slugged} module.
|
159
|
-
|
160
|
-
FriendlyId will generate slugs from a method or column that you specify, and
|
161
|
-
store them in a field in your model. By default, this field must be named
|
162
|
-
`:slug`, though you may change this using the
|
163
|
-
{FriendlyId::Slugged::Configuration#slug_column slug_column} configuration
|
164
|
-
option. You should add an index to this column, and in most cases, make it
|
165
|
-
unique. You may also wish to constrain it to NOT NULL, but this depends on your
|
166
|
-
app's behavior and requirements.
|
167
|
-
|
168
|
-
### Example Setup
|
169
|
-
|
170
|
-
# your model
|
171
|
-
class Post < ActiveRecord::Base
|
172
|
-
extend FriendlyId
|
173
|
-
friendly_id :title, :use => :slugged
|
174
|
-
validates_presence_of :title, :slug, :body
|
175
|
-
end
|
176
|
-
|
177
|
-
# a migration
|
178
|
-
class CreatePosts < ActiveRecord::Migration
|
179
|
-
def self.up
|
180
|
-
create_table :posts do |t|
|
181
|
-
t.string :title, :null => false
|
182
|
-
t.string :slug, :null => false
|
183
|
-
t.text :body
|
184
|
-
end
|
185
|
-
|
186
|
-
add_index :posts, :slug, :unique => true
|
187
|
-
end
|
188
|
-
|
189
|
-
def self.down
|
190
|
-
drop_table :posts
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
### Working With Slugs
|
195
|
-
|
196
|
-
#### Formatting
|
197
|
-
|
198
|
-
By default, FriendlyId uses Active Support's
|
199
|
-
[paramaterize](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
|
200
|
-
method to create slugs. This method will intelligently replace spaces with
|
201
|
-
dashes, and Unicode Latin characters with ASCII approximations:
|
202
|
-
|
203
|
-
movie = Movie.create! :title => "Der Preis fürs Überleben"
|
204
|
-
movie.slug #=> "der-preis-furs-uberleben"
|
205
|
-
|
206
|
-
#### Column or Method?
|
207
|
-
|
208
|
-
FriendlyId always uses a method as the basis of the slug text - not a column. It
|
209
|
-
first glance, this may sound confusing, but remember that Active Record provides
|
210
|
-
methods for each column in a model's associated table, and that's what
|
211
|
-
FriendlyId uses.
|
212
|
-
|
213
|
-
Here's an example of a class that uses a custom method to generate the slug:
|
214
|
-
|
215
|
-
class Person < ActiveRecord::Base
|
216
|
-
friendly_id :name_and_location
|
217
|
-
def name_and_location
|
218
|
-
"#{name} from #{location}"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
bob = Person.create! :name => "Bob Smith", :location => "New York City"
|
223
|
-
bob.friendly_id #=> "bob-smith-from-new-york-city"
|
224
|
-
|
225
|
-
FriendlyId refers to this internally as the "base" method.
|
226
|
-
|
227
|
-
#### Uniqueness
|
228
|
-
|
229
|
-
When you try to insert a record that would generate a duplicate friendly id,
|
230
|
-
FriendlyId will append a UUID to the generated slug to ensure uniqueness:
|
231
|
-
|
232
|
-
car = Car.create :title => "Peugot 206"
|
233
|
-
car2 = Car.create :title => "Peugot 206"
|
234
|
-
|
235
|
-
car.friendly_id #=> "peugot-206"
|
236
|
-
car2.friendly_id #=> "peugot-206-f9f3789a-daec-4156-af1d-fab81aa16ee5"
|
237
|
-
|
238
|
-
Previous versions of FriendlyId appended a numeric sequence a to make slugs
|
239
|
-
unique, but this was removed to simplify using FriendlyId in concurrent code.
|
240
|
-
|
241
|
-
#### Candidates
|
242
|
-
|
243
|
-
Since UUIDs are ugly, FriendlyId provides a "slug candidates" functionality to
|
244
|
-
let you specify alternate slugs to use in the event the one you want to use is
|
245
|
-
already taken. For example:
|
246
|
-
|
247
|
-
class Restaurant < ActiveRecord::Base
|
248
|
-
extend FriendlyId
|
249
|
-
friendly_id :slug_candidates, use: :slugged
|
250
|
-
|
251
|
-
# Try building a slug based on the following fields in
|
252
|
-
# increasing order of specificity.
|
253
|
-
def slug_candidates
|
254
|
-
[
|
255
|
-
:name,
|
256
|
-
[:name, :city],
|
257
|
-
[:name, :street, :city],
|
258
|
-
[:name, :street_number, :street, :city]
|
259
|
-
]
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
r1 = Restaurant.create! name: 'Plaza Diner', city: 'New Paltz'
|
264
|
-
r2 = Restaurant.create! name: 'Plaza Diner', city: 'Kingston'
|
265
|
-
|
266
|
-
r1.friendly_id #=> 'plaza-diner'
|
267
|
-
r2.friendly_id #=> 'plaza-diner-kingston'
|
268
|
-
|
269
|
-
To use candidates, make your FriendlyId base method return an array. The
|
270
|
-
method need not be named `slug_candidates`; it can be anything you want. The
|
271
|
-
array may contain any combination of symbols, strings, procs or lambdas and
|
272
|
-
will be evaluated lazily and in order. If you include symbols, FriendlyId will
|
273
|
-
invoke a method on your model class with the same name. Strings will be
|
274
|
-
interpreted literally. Procs and lambdas will be called and their return values
|
275
|
-
used as the basis of the friendly id. If none of the candidates can generate a
|
276
|
-
unique slug, then FriendlyId will append a UUID to the first candidate as a
|
277
|
-
last resort.
|
278
|
-
|
279
|
-
#### Sequence Separator
|
280
|
-
|
281
|
-
By default, FriendlyId uses a dash to separate the slug from a sequence.
|
282
|
-
|
283
|
-
You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
|
284
|
-
sequence_separator} configuration option.
|
285
|
-
|
286
|
-
#### Providing Your Own Slug Processing Method
|
287
|
-
|
288
|
-
You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
|
289
|
-
total control over the slug format. It will be invoked for any generated slug,
|
290
|
-
whether for a single slug or for slug candidates.
|
291
|
-
|
292
|
-
#### Deciding When to Generate New Slugs
|
293
|
-
|
294
|
-
As of FriendlyId 5.0, slugs are only generated when the `slug` field is nil. If
|
295
|
-
you want a slug to be regenerated,set the slug field to nil:
|
296
|
-
|
297
|
-
restaurant.friendly_id # joes-diner
|
298
|
-
restaurant.name = "The Plaza Diner"
|
299
|
-
restaurant.save!
|
300
|
-
restaurant.friendly_id # joes-diner
|
301
|
-
restaurant.slug = nil
|
302
|
-
restaurant.save!
|
303
|
-
restaurant.friendly_id # the-plaza-diner
|
304
|
-
|
305
|
-
You can also override the
|
306
|
-
{FriendlyId::Slugged#should_generate_new_friendly_id?} method, which lets you
|
307
|
-
control exactly when new friendly ids are set:
|
308
|
-
|
309
|
-
class Post < ActiveRecord::Base
|
310
|
-
extend FriendlyId
|
311
|
-
friendly_id :title, :use => :slugged
|
312
|
-
|
313
|
-
def should_generate_new_friendly_id?
|
314
|
-
title_changed?
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
#### Locale-specific Transliterations
|
319
|
-
|
320
|
-
Active Support's `parameterize` uses
|
321
|
-
[transliterate](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate),
|
322
|
-
which in turn can use I18n's transliteration rules to consider the current
|
323
|
-
locale when replacing Latin characters:
|
324
|
-
|
325
|
-
# config/locales/de.yml
|
326
|
-
de:
|
327
|
-
i18n:
|
328
|
-
transliterate:
|
329
|
-
rule:
|
330
|
-
ü: "ue"
|
331
|
-
ö: "oe"
|
332
|
-
etc...
|
333
|
-
|
334
|
-
movie = Movie.create! :title => "Der Preis fürs Überleben"
|
335
|
-
movie.slug #=> "der-preis-fuers-ueberleben"
|
336
|
-
|
337
|
-
This functionality was in fact taken from earlier versions of FriendlyId.
|
338
|
-
|
339
|
-
#### Gotchas: Common Problems
|
340
|
-
|
341
|
-
FriendlyId uses a before_validation callback to generate and set the slug. This
|
342
|
-
means that if you create two model instances before saving them, it's possible
|
343
|
-
they will generate the same slug, and the second save will fail.
|
344
|
-
|
345
|
-
This can happen in two fairly normal cases: the first, when a model using nested
|
346
|
-
attributes creates more than one record for a model that uses friendly_id. The
|
347
|
-
second, in concurrent code, either in threads or multiple processes.
|
348
|
-
|
349
|
-
To solve the nested attributes issue, I recommend simply avoiding them when
|
350
|
-
creating more than one nested record for a model that uses FriendlyId. See [this
|
351
|
-
Github issue](https://github.com/norman/friendly_id/issues/185) for discussion.
|
352
|
-
|
353
|
-
|
354
|
-
## History: Avoiding 404's When Slugs Change
|
355
|
-
|
356
|
-
FriendlyId's {FriendlyId::History History} module adds the ability to store a
|
357
|
-
log of a model's slugs, so that when its friendly id changes, it's still
|
358
|
-
possible to perform finds by the old id.
|
359
|
-
|
360
|
-
The primary use case for this is avoiding broken URLs.
|
361
|
-
|
362
|
-
### Setup
|
363
|
-
|
364
|
-
In order to use this module, you must add a table to your database schema to
|
365
|
-
store the slug records. FriendlyId provides a generator for this purpose:
|
366
|
-
|
367
|
-
rails generate friendly_id
|
368
|
-
rake db:migrate
|
369
|
-
|
370
|
-
This will add a table named `friendly_id_slugs`, used by the {FriendlyId::Slug}
|
371
|
-
model.
|
372
|
-
|
373
|
-
### Considerations
|
374
|
-
|
375
|
-
Because recording slug history requires creating additional database records,
|
376
|
-
this module has an impact on the performance of the associated model's `create`
|
377
|
-
method.
|
378
|
-
|
379
|
-
### Example
|
380
|
-
|
381
|
-
class Post < ActiveRecord::Base
|
382
|
-
extend FriendlyId
|
383
|
-
friendly_id :title, :use => :history
|
384
|
-
end
|
385
|
-
|
386
|
-
class PostsController < ApplicationController
|
387
|
-
|
388
|
-
before_filter :find_post
|
389
|
-
|
390
|
-
...
|
391
|
-
|
392
|
-
def find_post
|
393
|
-
@post = Post.find params[:id]
|
394
|
-
|
395
|
-
# If an old id or a numeric id was used to find the record, then
|
396
|
-
# the request path will not match the post_path, and we should do
|
397
|
-
# a 301 redirect that uses the current friendly id.
|
398
|
-
if request.path != post_path(@post)
|
399
|
-
return redirect_to @post, :status => :moved_permanently
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|
403
|
-
|
404
|
-
|
405
|
-
## Unique Slugs by Scope
|
406
|
-
|
407
|
-
The {FriendlyId::Scoped} module allows FriendlyId to generate unique slugs
|
408
|
-
within a scope.
|
409
|
-
|
410
|
-
This allows, for example, two restaurants in different cities to have the slug
|
411
|
-
`joes-diner`:
|
412
|
-
|
413
|
-
class Restaurant < ActiveRecord::Base
|
414
|
-
extend FriendlyId
|
415
|
-
belongs_to :city
|
416
|
-
friendly_id :name, :use => :scoped, :scope => :city
|
417
|
-
end
|
418
|
-
|
419
|
-
class City < ActiveRecord::Base
|
420
|
-
extend FriendlyId
|
421
|
-
has_many :restaurants
|
422
|
-
friendly_id :name, :use => :slugged
|
423
|
-
end
|
424
|
-
|
425
|
-
City.friendly.find("seattle").restaurants.friendly.find("joes-diner")
|
426
|
-
City.friendly.find("chicago").restaurants.friendly.find("joes-diner")
|
427
|
-
|
428
|
-
Without :scoped in this case, one of the restaurants would have the slug
|
429
|
-
`joes-diner` and the other would have `joes-diner-f9f3789a-daec-4156-af1d-fab81aa16ee5`.
|
430
|
-
|
431
|
-
The value for the `:scope` option can be the name of a `belongs_to` relation, or
|
432
|
-
a column.
|
433
|
-
|
434
|
-
Additionally, the `:scope` option can receive an array of scope values:
|
435
|
-
|
436
|
-
class Cuisine < ActiveRecord::Base
|
437
|
-
extend FriendlyId
|
438
|
-
has_many :restaurants
|
439
|
-
friendly_id :name, :use => :slugged
|
440
|
-
end
|
441
|
-
|
442
|
-
class City < ActiveRecord::Base
|
443
|
-
extend FriendlyId
|
444
|
-
has_many :restaurants
|
445
|
-
friendly_id :name, :use => :slugged
|
446
|
-
end
|
447
|
-
|
448
|
-
class Restaurant < ActiveRecord::Base
|
449
|
-
extend FriendlyId
|
450
|
-
belongs_to :city
|
451
|
-
friendly_id :name, :use => :scoped, :scope => [:city, :cuisine]
|
452
|
-
end
|
453
|
-
|
454
|
-
All supplied values will be used to determine scope.
|
455
|
-
|
456
|
-
### Finding Records by Friendly ID
|
457
|
-
|
458
|
-
If you are using scopes your friendly ids may not be unique, so a simple find
|
459
|
-
like
|
460
|
-
|
461
|
-
Restaurant.friendly.find("joes-diner")
|
462
|
-
|
463
|
-
may return the wrong record. In these cases it's best to query through the
|
464
|
-
relation:
|
465
|
-
|
466
|
-
@city.restaurants.friendly.find("joes-diner")
|
467
|
-
|
468
|
-
Alternatively, you could pass the scope value as a query parameter:
|
469
|
-
|
470
|
-
Restaurant.friendly.find("joes-diner").where(:city_id => @city.id)
|
471
|
-
|
472
|
-
|
473
|
-
### Finding All Records That Match a Scoped ID
|
474
|
-
|
475
|
-
Query the slug column directly:
|
476
|
-
|
477
|
-
Restaurant.where(:slug => "joes-diner")
|
478
|
-
|
479
|
-
### Routes for Scoped Models
|
480
|
-
|
481
|
-
Recall that FriendlyId is a database-centric library, and does not set up any
|
482
|
-
routes for scoped models. You must do this yourself in your application. Here's
|
483
|
-
an example of one way to set this up:
|
484
|
-
|
485
|
-
# in routes.rb
|
486
|
-
resources :cities do
|
487
|
-
resources :restaurants
|
488
|
-
end
|
489
|
-
|
490
|
-
# in views
|
491
|
-
<%= link_to 'Show', [@city, @restaurant] %>
|
492
|
-
|
493
|
-
# in controllers
|
494
|
-
@city = City.friendly.find(params[:city_id])
|
495
|
-
@restaurant = @city.restaurants.friendly.find(params[:id])
|
496
|
-
|
497
|
-
# URLs:
|
498
|
-
http://example.org/cities/seattle/restaurants/joes-diner
|
499
|
-
http://example.org/cities/chicago/restaurants/joes-diner
|
500
|
-
|
501
|
-
|
502
|
-
## Translating Slugs Using Simple I18n
|
503
|
-
|
504
|
-
The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to
|
505
|
-
FriendlyId.
|
506
|
-
|
507
|
-
In order to use this module, your model must have a slug column for each locale.
|
508
|
-
By default FriendlyId looks for columns named, for example, "slug_en",
|
509
|
-
"slug_es", etc. The first part of the name can be configured by passing the
|
510
|
-
`:slug_column` option if you choose. Note that the column for the default locale
|
511
|
-
must also include the locale in its name.
|
512
|
-
|
513
|
-
This module is most suitable to applications that need to support few locales.
|
514
|
-
If you need to support two or more locales, you may wish to use the
|
515
|
-
friendly_id_globalize gem instead.
|
516
|
-
|
517
|
-
### Example migration
|
518
|
-
|
519
|
-
def self.up
|
520
|
-
create_table :posts do |t|
|
521
|
-
t.string :title
|
522
|
-
t.string :slug_en
|
523
|
-
t.string :slug_es
|
524
|
-
t.text :body
|
525
|
-
end
|
526
|
-
add_index :posts, :slug_en
|
527
|
-
add_index :posts, :slug_es
|
528
|
-
end
|
529
|
-
|
530
|
-
### Finds
|
531
|
-
|
532
|
-
Finds will take into consideration the current locale:
|
533
|
-
|
534
|
-
I18n.locale = :es
|
535
|
-
Post.find("la-guerra-de-las-galaxas")
|
536
|
-
I18n.locale = :en
|
537
|
-
Post.find("star-wars")
|
538
|
-
|
539
|
-
To find a slug by an explicit locale, perform the find inside a block
|
540
|
-
passed to I18n's `with_locale` method:
|
541
|
-
|
542
|
-
I18n.with_locale(:es) do
|
543
|
-
Post.find("la-guerra-de-las-galaxas")
|
544
|
-
end
|
545
|
-
|
546
|
-
### Creating Records
|
547
|
-
|
548
|
-
When new records are created, the slug is generated for the current locale only.
|
549
|
-
|
550
|
-
### Translating Slugs
|
551
|
-
|
552
|
-
To translate an existing record's friendly_id, use
|
553
|
-
{FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug
|
554
|
-
you add is properly escaped, transliterated and sequenced:
|
555
|
-
|
556
|
-
post = Post.create :name => "Star Wars"
|
557
|
-
post.set_friendly_id("La guerra de las galaxas", :es)
|
558
|
-
|
559
|
-
If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the
|
560
|
-
current locale:
|
561
|
-
|
562
|
-
I18n.with_locale(:es) do
|
563
|
-
post.set_friendly_id("La guerra de las galaxas")
|
564
|
-
end
|
565
|
-
|
566
|
-
|
567
|
-
## Reserved Words
|
568
|
-
|
569
|
-
The {FriendlyId::Reserved Reserved} module adds the ability to exlude a list of
|
570
|
-
words from use as FriendlyId slugs.
|
571
|
-
|
572
|
-
By default, FriendlyId reserves the words "new" and "edit" when this module is
|
573
|
-
included. You can configure this globally by using {FriendlyId.defaults
|
574
|
-
FriendlyId.defaults}:
|
575
|
-
|
576
|
-
FriendlyId.defaults do |config|
|
577
|
-
config.use :reserved
|
578
|
-
# Reserve words for English and Spanish URLs
|
579
|
-
config.reserved_words = %w(new edit nueva nuevo editar)
|
580
|
-
end
|
581
|
-
|
582
|
-
Note that the error message will appear on the field `:friendly_id`. If you are
|
583
|
-
using Rails's scaffolded form errors display, then it will have no field to
|
584
|
-
highlight. If you'd like to change this so that scaffolding works as expected,
|
585
|
-
one way to accomplish this is to move the error message to a different field.
|
586
|
-
For example:
|
587
|
-
|
588
|
-
class Person < ActiveRecord::Base
|
589
|
-
extend FriendlyId
|
590
|
-
friendly_id :name, use: :slugged
|
591
|
-
|
592
|
-
after_validation :move_friendly_id_error_to_name
|
593
|
-
|
594
|
-
def move_friendly_id_error_to_name
|
595
|
-
errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present?
|
596
|
-
end
|
597
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
require File.expand_path("../../../helper", __FILE__)
|
2
|
-
|
3
|
-
require "ancestry"
|
4
|
-
|
5
|
-
ActiveRecord::Migration.create_table("things") do |t|
|
6
|
-
t.string :name
|
7
|
-
t.string :slug
|
8
|
-
t.string :ancestry
|
9
|
-
end
|
10
|
-
ActiveRecord::Migration.add_index :things, :ancestry
|
11
|
-
|
12
|
-
class Thing < ActiveRecord::Base
|
13
|
-
extend FriendlyId
|
14
|
-
friendly_id do |config|
|
15
|
-
config.use :slugged
|
16
|
-
config.use :scoped
|
17
|
-
config.base = :name
|
18
|
-
config.scope = :ancestry
|
19
|
-
end
|
20
|
-
has_ancestry
|
21
|
-
end
|
22
|
-
|
23
|
-
class AncestryTest < MiniTest::Unit::TestCase
|
24
|
-
include FriendlyId::Test
|
25
|
-
|
26
|
-
test "should sequence slugs when scoped by ancestry" do
|
27
|
-
3.times.inject([]) do |memo, _|
|
28
|
-
memo << Thing.create!(:name => "a", :parent => memo.last)
|
29
|
-
end.each do |thing|
|
30
|
-
assert_equal "a", thing.friendly_id
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
@@ -1,45 +0,0 @@
|
|
1
|
-
ENV["DB"] = "postgres"
|
2
|
-
|
3
|
-
require "thread"
|
4
|
-
require File.expand_path("../../../helper", __FILE__)
|
5
|
-
require "active_record/locking/fatalistic"
|
6
|
-
|
7
|
-
ActiveRecord::Migration.tap do |m|
|
8
|
-
m.drop_table "things"
|
9
|
-
m.create_table("things") do |t|
|
10
|
-
t.string :name
|
11
|
-
t.string :slug
|
12
|
-
end
|
13
|
-
m.add_index :things, :slug, :unique => true
|
14
|
-
end
|
15
|
-
|
16
|
-
class Thing < ActiveRecord::Base
|
17
|
-
extend FriendlyId
|
18
|
-
friendly_id :name, :use => :slugged
|
19
|
-
end
|
20
|
-
|
21
|
-
$things = 10.times.map do
|
22
|
-
Thing.new :name => "a b c"
|
23
|
-
end
|
24
|
-
|
25
|
-
$mutex = Mutex.new
|
26
|
-
|
27
|
-
def save_thing
|
28
|
-
thing = $mutex.synchronize do
|
29
|
-
$things.pop
|
30
|
-
end
|
31
|
-
if thing.nil? then return end
|
32
|
-
Thing.lock do
|
33
|
-
thing.save!
|
34
|
-
print "#{thing.friendly_id}\n"
|
35
|
-
end
|
36
|
-
true
|
37
|
-
end
|
38
|
-
|
39
|
-
2.times.map do
|
40
|
-
Thread.new do
|
41
|
-
while true do
|
42
|
-
break unless save_thing
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end.map(&:value)
|