friendly_id4 4.0.0.pre → 4.0.0.pre3
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.
- data/.gitignore +9 -0
- data/ABOUT.md +139 -0
- data/Guide.md +405 -0
- data/README.md +65 -47
- data/Rakefile +12 -16
- data/bench.rb +71 -0
- data/friendly_id.gemspec +29 -0
- data/lib/friendly_id.rb +9 -116
- data/lib/friendly_id/base.rb +29 -0
- data/lib/friendly_id/configuration.rb +31 -0
- data/lib/friendly_id/finder_methods.rb +14 -0
- data/lib/friendly_id/history.rb +29 -0
- data/lib/friendly_id/migration.rb +17 -0
- data/lib/friendly_id/model.rb +22 -0
- data/lib/friendly_id/object_utils.rb +27 -0
- data/lib/friendly_id/scoped.rb +24 -2
- data/lib/friendly_id/slug_sequencer.rb +79 -0
- data/lib/friendly_id/slugged.rb +11 -84
- data/lib/friendly_id/version.rb +1 -1
- data/lib/generators/friendly_id_generator.rb +21 -0
- data/test/core_test.rb +23 -56
- data/test/helper.rb +41 -0
- data/test/history_test.rb +46 -0
- data/test/object_utils_test.rb +18 -0
- data/test/scoped_test.rb +30 -49
- data/test/shared.rb +47 -0
- data/test/slugged_test.rb +31 -52
- metadata +56 -22
- data/lib/friendly_id/test.rb +0 -23
- data/lib/friendly_id/test/generic.rb +0 -84
- data/test/test_helper.rb +0 -23
data/README.md
CHANGED
@@ -1,74 +1,92 @@
|
|
1
|
-
|
1
|
+
<hr>
|
2
|
+
**NOTE** This is FriendlyId4 - a rewrite of FriendlyId. For more info about this
|
3
|
+
rewrite, and the changes it brings, read [this
|
4
|
+
document](https://github.com/norman/friendly_id_4/blob/master/ABOUT.md).
|
2
5
|
|
3
|
-
|
4
|
-
released some time in August or September 2011, once I've had the chance to
|
5
|
-
actually use it in a real website for a while.
|
6
|
+
For the current stable FriendlyId, please see:
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
[https://github.com/norman/friendly_id](https://github.com/norman/friendly_id_4)
|
9
|
+
<hr>
|
10
|
+
# FriendlyId
|
9
11
|
|
10
|
-
|
12
|
+
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
|
13
|
+
Ruby on Rails. It allows you to create pretty URL's and work with
|
14
|
+
human-friendly strings as if they were numeric ids for Active Record models.
|
11
15
|
|
12
|
-
|
16
|
+
Using FriendlyId, it's easy to make your application use URL's like:
|
13
17
|
|
14
|
-
|
15
|
-
features, but makes it possible to add them back as addons. We can also remove
|
16
|
-
some complexity by relying on the better default functionality provided by newer
|
17
|
-
versions of Active Support and Active Record.
|
18
|
+
http://example.com/states/washington
|
18
19
|
|
19
|
-
|
20
|
+
instead of:
|
20
21
|
|
21
|
-
|
22
|
+
http://example.com/states/4323454
|
22
23
|
|
23
|
-
##
|
24
|
+
## FriendlyId Features
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
FriendlyId offers many advanced features, including: slug history and
|
27
|
+
versioning, scoped slugs, reserved words, custom slug generators, and
|
28
|
+
excellent Unicode support. For complete information on using FriendlyId,
|
29
|
+
please see the [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html).
|
27
30
|
|
28
|
-
|
31
|
+
FriendlyId is compatible with Active Record **3.0** and **3.1**.
|
29
32
|
|
30
|
-
|
33
|
+
## Docs, Info and Support
|
31
34
|
|
32
|
-
FriendlyId
|
33
|
-
|
34
|
-
|
35
|
+
* [FriendlyId Guide](http://norman.github.com/friendly_id/file.Guide.html)
|
36
|
+
* [API Docs](http://norman.github.com/friendly_id)
|
37
|
+
* [Google Group](http://groups.google.com/group/friendly_id)
|
38
|
+
* [Source Code](http://github.com/norman/friendly_id/)
|
39
|
+
* [Issue Tracker](http://github.com/norman/friendly_id/issues)
|
35
40
|
|
36
|
-
##
|
41
|
+
## Rails Quickstart
|
37
42
|
|
38
|
-
|
39
|
-
generated slug value in the model table, which is simpler, faster and what most
|
40
|
-
people seem to want. Keeping slugs in a separate table is an optional add-on for
|
41
|
-
FriendlyId 4 (not implemented yet).
|
43
|
+
gem install friendly_id
|
42
44
|
|
43
|
-
|
45
|
+
rails new my_app
|
44
46
|
|
45
|
-
|
46
|
-
or non-friendly id was used to find the record, so that you could decide whether
|
47
|
-
to permanently redirect to the canonical URL. However, there's a simpler way to
|
48
|
-
do that, so this feature has been removed:
|
47
|
+
cd my_app
|
49
48
|
|
50
|
-
|
51
|
-
|
49
|
+
# add to Gemfile
|
50
|
+
gem "friendly_id", "~> 4.0.0"
|
51
|
+
|
52
|
+
rails generate scaffold user name:string slug:string
|
53
|
+
|
54
|
+
# edit db/migrate/*_create_users.rb
|
55
|
+
add_index :users, :slug, :unique => true
|
56
|
+
|
57
|
+
rake db:migrate
|
58
|
+
|
59
|
+
# edit app/models/user.rb
|
60
|
+
class User < ActiveRecord::Base
|
61
|
+
include FriendlyId::Slugged
|
62
|
+
has_friendly_id :name
|
52
63
|
end
|
53
64
|
|
54
|
-
|
65
|
+
User.create! :name => "Joe Schmoe"
|
66
|
+
|
67
|
+
rails server
|
68
|
+
|
69
|
+
GET http://localhost:3000/users/joe-schmoe
|
55
70
|
|
56
|
-
|
57
|
-
Person.find ["joe-schmoe", "john-doe"] # No longer supported
|
71
|
+
## Bugs
|
58
72
|
|
59
|
-
|
73
|
+
Please report them on the [Github issue tracker](http://github.com/norman/friendly_id/issues)
|
74
|
+
for this project.
|
60
75
|
|
61
|
-
|
76
|
+
If you have a bug to report, please include the following information:
|
62
77
|
|
63
|
-
|
78
|
+
* **Version information for FriendlyId, Rails and Ruby.**
|
79
|
+
* Stack trace and error message.
|
80
|
+
* Any snippets of relevant model, view or controller code that shows how your
|
81
|
+
are using FriendlyId.
|
64
82
|
|
65
|
-
|
83
|
+
If you are able to, it helps even more if you can fork FriendlyId on Github,
|
84
|
+
and add a test that reproduces the error you are experiencing.
|
66
85
|
|
67
|
-
|
68
|
-
by Active Record. FriendlyId still reserves "new" and "edit" by default to avoid
|
69
|
-
routing problems.
|
86
|
+
## Credits
|
70
87
|
|
71
|
-
|
88
|
+
FriendlyId was created by Norman Clarke, Adrian Mugnolo, and Emilio Tagua, and
|
89
|
+
has had significant contributions over the years from [many
|
90
|
+
volunteers](https://github.com/norman/friendly_id/contributors).
|
72
91
|
|
73
|
-
|
74
|
-
`FriendlyId::Configuration::DEFAULTS[:reserved_words]`.
|
92
|
+
Copyright (c) 2008-2011 Norman Clarke, released under the MIT license.
|
data/Rakefile
CHANGED
@@ -1,24 +1,20 @@
|
|
1
|
-
require "
|
2
|
-
require "rake/testtask"
|
3
|
-
require "rake/gempackagetask"
|
4
|
-
require "rake/clean"
|
1
|
+
require "cutest"
|
5
2
|
|
6
3
|
task :default => :test
|
4
|
+
task :test do
|
5
|
+
Cutest.run(Dir["test/*_test.rb"])
|
6
|
+
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
task :clean do
|
9
|
+
%x{rm -rf *.gem doc}
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
Reek::Rake::Task.new do |t|
|
15
|
-
t.fail_on_error = false
|
16
|
-
end
|
17
|
-
rescue LoadError
|
12
|
+
task :gem do
|
13
|
+
%x{gem build friendly_id.gemspec}
|
18
14
|
end
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
Rake::GemPackageTask.new(eval(File.read("friendly_id.gemspec"))) { |pkg| }
|
16
|
+
task :yard do
|
17
|
+
%x{yard doc}
|
23
18
|
end
|
24
19
|
|
20
|
+
task :doc => :yard
|
data/bench.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path("../test/helper", __FILE__)
|
2
|
+
require "ffaker"
|
3
|
+
require "friendly_id/migration"
|
4
|
+
|
5
|
+
N = 1000
|
6
|
+
|
7
|
+
migration do |m|
|
8
|
+
m.add_column :users, :slug, :string
|
9
|
+
m.add_index :users, :slug, :unique => true
|
10
|
+
end
|
11
|
+
|
12
|
+
migration do |m|
|
13
|
+
m.create_table :posts do |t|
|
14
|
+
t.string :name
|
15
|
+
t.string :slug
|
16
|
+
end
|
17
|
+
m.add_index :posts, :slug, :unique => true
|
18
|
+
end
|
19
|
+
CreateFriendlyIdSlugs.up
|
20
|
+
|
21
|
+
|
22
|
+
class Array
|
23
|
+
def rand
|
24
|
+
self[Kernel.rand(length)]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class User
|
29
|
+
include FriendlyId::Slugged
|
30
|
+
has_friendly_id :name
|
31
|
+
end
|
32
|
+
|
33
|
+
class Post < ActiveRecord::Base
|
34
|
+
include FriendlyId::History
|
35
|
+
has_friendly_id :name
|
36
|
+
end
|
37
|
+
|
38
|
+
USERS = []
|
39
|
+
BOOKS = []
|
40
|
+
POSTS = []
|
41
|
+
|
42
|
+
100.times do
|
43
|
+
name = Faker::Name.name
|
44
|
+
USERS << (User.create! :name => name).friendly_id
|
45
|
+
POSTS << (Post.create! :name => name).friendly_id
|
46
|
+
BOOKS << (Book.create! :name => name).id
|
47
|
+
end
|
48
|
+
|
49
|
+
Benchmark.bmbm do |x|
|
50
|
+
x.report 'find (without FriendlyId)' do
|
51
|
+
N.times {Book.find BOOKS.rand}
|
52
|
+
end
|
53
|
+
x.report 'find (in-table slug)' do
|
54
|
+
N.times {User.find USERS.rand}
|
55
|
+
end
|
56
|
+
x.report 'find (external slug)' do
|
57
|
+
N.times {Post.find_by_friendly_id POSTS.rand}
|
58
|
+
end
|
59
|
+
|
60
|
+
x.report 'insert (without FriendlyId)' do
|
61
|
+
N.times {transaction {Book.create :name => Faker::Name.name}}
|
62
|
+
end
|
63
|
+
|
64
|
+
x.report 'insert (in-table-slug)' do
|
65
|
+
N.times {transaction {User.create :name => Faker::Name.name}}
|
66
|
+
end
|
67
|
+
|
68
|
+
x.report 'insert (external slug)' do
|
69
|
+
N.times {transaction {Post.create :name => Faker::Name.name}}
|
70
|
+
end
|
71
|
+
end
|
data/friendly_id.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "friendly_id/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "friendly_id4"
|
8
|
+
s.version = FriendlyId::Version::STRING
|
9
|
+
s.authors = ["Norman Clarke"]
|
10
|
+
s.email = ["norman@njclarke.com"]
|
11
|
+
s.homepage = "http://norman.github.com/friendly_id"
|
12
|
+
s.summary = "A comprehensive slugging and pretty-URL plugin."
|
13
|
+
s.rubyforge_project = "friendly_id"
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency "activerecord", "~> 3.0"
|
19
|
+
s.add_development_dependency "sqlite3", "~> 1.3"
|
20
|
+
s.add_development_dependency "cutest", "~> 1.1.2"
|
21
|
+
s.add_development_dependency "ffaker"
|
22
|
+
s.add_development_dependency "yard"
|
23
|
+
|
24
|
+
s.description = <<-EOM
|
25
|
+
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins
|
26
|
+
for Ruby on Rails. It allows you to create pretty URL's and work with
|
27
|
+
human-friendly strings as if they were numeric ids for ActiveRecord models.
|
28
|
+
EOM
|
29
|
+
end
|
data/lib/friendly_id.rb
CHANGED
@@ -1,121 +1,14 @@
|
|
1
|
+
require "friendly_id/base"
|
2
|
+
require "friendly_id/model"
|
3
|
+
require "friendly_id/object_utils"
|
4
|
+
require "friendly_id/configuration"
|
5
|
+
require "friendly_id/finder_methods"
|
6
|
+
|
1
7
|
# FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
|
2
8
|
# slugs.
|
3
9
|
# @author Norman Clarke
|
4
10
|
module FriendlyId
|
5
|
-
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
|
9
|
-
# Class methods that will be added to ActiveRecord::Base.
|
10
|
-
module Base
|
11
|
-
extend self
|
12
|
-
|
13
|
-
def has_friendly_id(*args)
|
14
|
-
options = args.extract_options!
|
15
|
-
base = args.shift
|
16
|
-
friendly_id_config.set options.merge(:base => base)
|
17
|
-
include Model
|
18
|
-
# @NOTE: AR-specific code here
|
19
|
-
validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
|
20
|
-
before_save do |record|
|
21
|
-
record.instance_eval {@_current_friendly_id = friendly_id}
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def friendly_id_config
|
26
|
-
@friendly_id_config ||= Configuration.new(self)
|
27
|
-
end
|
28
|
-
|
29
|
-
def uses_friendly_id?
|
30
|
-
!! @friendly_id_config
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# Instance methods that will be added to all classes using FriendlyId.
|
35
|
-
module Model
|
36
|
-
|
37
|
-
# Convenience method for accessing the class method of the same name.
|
38
|
-
def friendly_id_config
|
39
|
-
self.class.friendly_id_config
|
40
|
-
end
|
41
|
-
|
42
|
-
# Get the instance's friendly_id.
|
43
|
-
def friendly_id
|
44
|
-
send friendly_id_config.query_field
|
45
|
-
end
|
46
|
-
|
47
|
-
# Either the friendly_id, or the numeric id cast to a string.
|
48
|
-
def to_param
|
49
|
-
(friendly_id or id).to_s
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# The configuration paramters passed to +has_friendly_id+ will be stored
|
54
|
-
# in this object.
|
55
|
-
class Configuration
|
56
|
-
attr_accessor :base
|
57
|
-
attr_reader :klass
|
58
|
-
|
59
|
-
DEFAULTS = {
|
60
|
-
:config_error_message => 'FriendlyId has no such config option "%s"',
|
61
|
-
:reserved_words => ["new", "edit"]
|
62
|
-
}
|
63
|
-
|
64
|
-
def initialize(klass, values = nil)
|
65
|
-
@klass = klass
|
66
|
-
set values
|
67
|
-
end
|
68
|
-
|
69
|
-
def method_missing(symbol, *args, &block)
|
70
|
-
option = symbol.to_s.gsub(/=\z/, '')
|
71
|
-
raise ArgumentError, DEFAULTS[:config_error_message] % option
|
72
|
-
end
|
73
|
-
|
74
|
-
def set(values)
|
75
|
-
values and values.each {|name, value| self.send "#{name}=", value}
|
76
|
-
end
|
77
|
-
|
78
|
-
def query_field
|
79
|
-
base
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Utility methods that are in Object because it's impossible to predict what
|
84
|
-
# kinds of objects get passed into FinderMethods#find_one and
|
85
|
-
# Model#normalize_friendly_id.
|
86
|
-
module ObjectUtils
|
87
|
-
|
88
|
-
# True is the id is definitely friendly, false if definitely unfriendly,
|
89
|
-
# else nil.
|
90
|
-
def friendly_id?
|
91
|
-
if kind_of?(Integer) or kind_of?(Symbol) or self.class.respond_to? :friendly_id_config
|
92
|
-
false
|
93
|
-
elsif to_i.to_s != to_s
|
94
|
-
true
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# True if the id is definitely unfriendly, false if definitely friendly,
|
99
|
-
# else nil.
|
100
|
-
def unfriendly_id?
|
101
|
-
val = friendly_id? ; !val unless val.nil?
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
# These methods will override the finder methods in ActiveRecord::Relation.
|
106
|
-
module FinderMethods
|
107
|
-
|
108
|
-
protected
|
109
|
-
|
110
|
-
# @NOTE AR-specific code here
|
111
|
-
def find_one(id)
|
112
|
-
return super if !@klass.uses_friendly_id? or id.unfriendly_id?
|
113
|
-
where(@klass.friendly_id_config.query_field => id).first or super
|
114
|
-
end
|
115
|
-
|
116
|
-
end
|
11
|
+
autoload :Slugged, "friendly_id/slugged"
|
12
|
+
autoload :Scoped, "friendly_id/scoped"
|
13
|
+
autoload :History, "friendly_id/history"
|
117
14
|
end
|
118
|
-
|
119
|
-
ActiveRecord::Base.extend FriendlyId::Base
|
120
|
-
ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
|
121
|
-
Object.send :include, FriendlyId::ObjectUtils
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
# Class methods that will be added to ActiveRecord::Base.
|
3
|
+
module Base
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def has_friendly_id(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
base = args.shift
|
9
|
+
friendly_id_config.set options.merge(:base => base)
|
10
|
+
include Model
|
11
|
+
# @NOTE: AR-specific code here
|
12
|
+
validates_exclusion_of base, :in => Configuration::DEFAULTS[:reserved_words]
|
13
|
+
before_save do |record|
|
14
|
+
record.instance_eval {@current_friendly_id = friendly_id}
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def friendly_id_config
|
20
|
+
@friendly_id_config ||= Configuration.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def uses_friendly_id?
|
24
|
+
defined? @friendly_id_config
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ActiveRecord::Base.extend FriendlyId::Base
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module FriendlyId
|
2
|
+
# The configuration paramters passed to +has_friendly_id+ will be stored
|
3
|
+
# in this object.
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :base
|
6
|
+
attr_reader :klass
|
7
|
+
|
8
|
+
DEFAULTS = {
|
9
|
+
:config_error_message => 'FriendlyId has no such config option "%s"',
|
10
|
+
:reserved_words => ["new", "edit"]
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(klass, values = nil)
|
14
|
+
@klass = klass
|
15
|
+
set values
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(symbol, *args, &block)
|
19
|
+
option = symbol.to_s.gsub(/=\z/, '')
|
20
|
+
raise ArgumentError, DEFAULTS[:config_error_message] % option
|
21
|
+
end
|
22
|
+
|
23
|
+
def set(values)
|
24
|
+
values and values.each {|name, value| self.send "#{name}=", value}
|
25
|
+
end
|
26
|
+
|
27
|
+
def query_field
|
28
|
+
base
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|