friendly_id4 4.0.0.beta4 → 4.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +4 -0
- data/README.md +21 -24
- data/Rakefile +1 -1
- data/friendly_id.gemspec +2 -2
- data/lib/friendly_id.rb +102 -8
- data/lib/friendly_id/base.rb +76 -8
- data/lib/friendly_id/configuration.rb +9 -27
- data/lib/friendly_id/finder_methods.rb +8 -0
- data/lib/friendly_id/history.rb +35 -0
- data/lib/friendly_id/migration.rb +1 -0
- data/lib/friendly_id/object_utils.rb +16 -6
- data/lib/friendly_id/reserved.rb +46 -0
- data/lib/friendly_id/scoped.rb +101 -19
- data/lib/friendly_id/slug.rb +3 -0
- data/lib/friendly_id/slug_sequencer.rb +3 -0
- data/lib/friendly_id/slugged.rb +180 -8
- data/lib/generators/friendly_id_generator.rb +3 -0
- data/test/base_test.rb +23 -0
- data/test/configuration_test.rb +6 -6
- data/test/core_test.rb +3 -11
- data/test/helper.rb +2 -2
- data/test/history_test.rb +9 -9
- data/test/object_utils_test.rb +3 -3
- data/test/reserved_test.rb +26 -0
- data/test/scoped_test.rb +8 -8
- data/test/shared.rb +15 -15
- data/test/slugged_test.rb +30 -20
- metadata +19 -18
- data/Guide.md +0 -363
- data/lib/friendly_id/version.rb +0 -9
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -1,17 +1,8 @@
|
|
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).
|
5
|
-
|
6
|
-
For the current stable FriendlyId, please see:
|
7
|
-
|
8
|
-
[https://github.com/norman/friendly_id](https://github.com/norman/friendly_id_4)
|
9
|
-
<hr>
|
10
1
|
# FriendlyId
|
11
2
|
|
12
3
|
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
|
-
|
4
|
+
Ruby on Rails. It allows you to create pretty URL's and work with human-friendly
|
5
|
+
strings as if they were numeric ids for Active Record models.
|
15
6
|
|
16
7
|
Using FriendlyId, it's easy to make your application use URL's like:
|
17
8
|
|
@@ -24,20 +15,10 @@ instead of:
|
|
24
15
|
## FriendlyId Features
|
25
16
|
|
26
17
|
FriendlyId offers many advanced features, including: slug history and
|
27
|
-
versioning, scoped slugs, reserved words, custom slug generators
|
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).
|
18
|
+
versioning, scoped slugs, reserved words, and custom slug generators.
|
30
19
|
|
31
20
|
FriendlyId is compatible with Active Record **3.0** and **3.1**.
|
32
21
|
|
33
|
-
## Docs, Info and Support
|
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)
|
40
|
-
|
41
22
|
## Rails Quickstart
|
42
23
|
|
43
24
|
gem install friendly_id
|
@@ -46,8 +27,10 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
|
|
46
27
|
|
47
28
|
cd my_app
|
48
29
|
|
49
|
-
#
|
50
|
-
|
30
|
+
# Add to Gemfile - this will change once version 4 is no longer
|
31
|
+
# in beta, but for now do this:
|
32
|
+
gem "friendly_id4", "4.0.0.beta4", :require => "friendly_id"
|
33
|
+
|
51
34
|
|
52
35
|
rails generate scaffold user name:string slug:string
|
53
36
|
|
@@ -68,6 +51,20 @@ FriendlyId is compatible with Active Record **3.0** and **3.1**.
|
|
68
51
|
|
69
52
|
GET http://localhost:3000/users/joe-schmoe
|
70
53
|
|
54
|
+
|
55
|
+
### Future Compatibility
|
56
|
+
|
57
|
+
FriendlyId will always remain compatible with the current release of Rails, and
|
58
|
+
at least one stable release behind. That means that support for 3.0.x will not be
|
59
|
+
dropped until a stable release of 3.2 is out, or possibly longer.
|
60
|
+
|
61
|
+
|
62
|
+
## Benchmarks
|
63
|
+
|
64
|
+
The latest benchmarks for FriendlyId are maintained
|
65
|
+
[here](https://gist.github.com/1129745).
|
66
|
+
|
67
|
+
|
71
68
|
## Bugs
|
72
69
|
|
73
70
|
Please report them on the [Github issue
|
data/Rakefile
CHANGED
data/friendly_id.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
3
|
|
4
|
-
require "friendly_id
|
4
|
+
require "friendly_id"
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "friendly_id4"
|
8
|
-
s.version = FriendlyId::
|
8
|
+
s.version = FriendlyId::VERSION
|
9
9
|
s.authors = ["Norman Clarke"]
|
10
10
|
s.email = ["norman@njclarke.com"]
|
11
11
|
s.homepage = "http://norman.github.com/friendly_id"
|
data/lib/friendly_id.rb
CHANGED
@@ -1,17 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "thread"
|
1
3
|
require "friendly_id/base"
|
2
4
|
require "friendly_id/model"
|
3
5
|
require "friendly_id/object_utils"
|
4
6
|
require "friendly_id/configuration"
|
5
7
|
require "friendly_id/finder_methods"
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
=begin
|
10
|
+
|
11
|
+
== About FriendlyId
|
12
|
+
|
13
|
+
FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
|
14
|
+
in your URLs with strings:
|
15
|
+
|
16
|
+
# without FriendlyId
|
17
|
+
http://example.com/states/4323454
|
18
|
+
|
19
|
+
# with FriendlyId
|
20
|
+
http://example.com/states/washington
|
21
|
+
|
22
|
+
It requires few changes to your application code and offers flexibility,
|
23
|
+
performance and a well-documented codebase.
|
24
|
+
|
25
|
+
=== Concepts
|
26
|
+
|
27
|
+
Although FriendlyId helps with URLs, it does all of its work inside your models,
|
28
|
+
not your routes.
|
29
|
+
|
30
|
+
=== Simple Models
|
31
|
+
|
32
|
+
The simplest way to use FriendlyId is with a model that has a uniquely indexed
|
33
|
+
column with no spaces or special characters, and that is seldom or never
|
34
|
+
updated. The most common example of this is a user name:
|
35
|
+
|
36
|
+
class User < ActiveRecord::Base
|
37
|
+
extend FriendlyId
|
38
|
+
friendly_id :login
|
39
|
+
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
40
|
+
end
|
41
|
+
|
42
|
+
@user = User.find "joe" # the old User.find(1) still works, too
|
43
|
+
@user.to_param # returns "joe"
|
44
|
+
redirect_to @user # the URL will be /users/joe
|
45
|
+
|
46
|
+
In this case, FriendlyId assumes you want to use the column as-is; it will never
|
47
|
+
modify the value of the column, and your application should ensure that the
|
48
|
+
value is admissible in a URL:
|
49
|
+
|
50
|
+
class City < ActiveRecord::Base
|
51
|
+
extend FriendlyId
|
52
|
+
friendly_id :name
|
53
|
+
end
|
54
|
+
|
55
|
+
@city.find "Viña del Mar"
|
56
|
+
redirect_to @city # the URL will be /cities/Viña%20del%20Mar
|
57
|
+
|
58
|
+
For this reason, it is often more convenient to use "slugs" rather than a single
|
59
|
+
column.
|
60
|
+
|
61
|
+
=== Slugged Models
|
62
|
+
|
63
|
+
FriendlyId can uses a separate column to store slugs for models which require
|
64
|
+
some processing of the friendly_id text. The most common example is a blog
|
65
|
+
post's title, which may have spaces, uppercase characters, or other attributes
|
66
|
+
you wish to modify to make them more suitable for use in URL's.
|
67
|
+
|
68
|
+
class Post < ActiveRecord::Base
|
69
|
+
extend FriendlyId
|
70
|
+
friendly_id :title, :use => :slugged
|
71
|
+
end
|
72
|
+
|
73
|
+
@post = Post.create(:title => "This is the first post!")
|
74
|
+
@post.friendly_id # returns "this-is-the-first-post"
|
75
|
+
redirect_to @post # the URL will be /posts/this-is-the-first-post
|
76
|
+
|
77
|
+
In general, use slugs by default unless you know for sure you don't need them.
|
78
|
+
|
79
|
+
@author Norman Clarke
|
80
|
+
=end
|
11
81
|
module FriendlyId
|
12
|
-
|
13
|
-
|
82
|
+
|
83
|
+
# The current version.
|
84
|
+
VERSION = "4.0.0.beta5"
|
85
|
+
|
86
|
+
@mutex = Mutex.new
|
87
|
+
|
14
88
|
autoload :History, "friendly_id/history"
|
89
|
+
autoload :Reserved, "friendly_id/reserved"
|
90
|
+
autoload :Scoped, "friendly_id/scoped"
|
91
|
+
autoload :Slugged, "friendly_id/slugged"
|
15
92
|
|
16
93
|
# FriendlyId takes advantage of `extended` to do basic model setup, primarily
|
17
94
|
# extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
|
@@ -20,7 +97,7 @@ module FriendlyId
|
|
20
97
|
# Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
|
21
98
|
# version tries to be less invasive.
|
22
99
|
#
|
23
|
-
# In addition to adding {FriendlyId::Base
|
100
|
+
# In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
|
24
101
|
# instance variable +@friendly_id_config+ is added. This variable is an
|
25
102
|
# instance of an anonymous subclass of {FriendlyId::Configuration}. This
|
26
103
|
# allows subsequently loaded modules like {FriendlyId::Slugged} and
|
@@ -39,7 +116,24 @@ module FriendlyId
|
|
39
116
|
base.instance_eval do
|
40
117
|
extend FriendlyId::Base
|
41
118
|
@friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
|
119
|
+
if defaults = FriendlyId.defaults
|
120
|
+
defaults.yield @friendly_id_config
|
121
|
+
end
|
42
122
|
end
|
43
123
|
ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
|
44
124
|
end
|
45
|
-
|
125
|
+
|
126
|
+
# Set global defaults for all models using FriendlyId.
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# FriendlyId.defaults do |config|
|
130
|
+
# config.base = :name
|
131
|
+
# config.use :slugged
|
132
|
+
# end
|
133
|
+
def self.defaults(&block)
|
134
|
+
@mutex.synchronize do
|
135
|
+
@defaults = block if block_given?
|
136
|
+
end
|
137
|
+
@defaults
|
138
|
+
end
|
139
|
+
end
|
data/lib/friendly_id/base.rb
CHANGED
@@ -1,31 +1,99 @@
|
|
1
1
|
module FriendlyId
|
2
|
-
# Class methods that will be added to
|
2
|
+
# Class methods that will be added to model classes that extend {FriendlyId}.
|
3
3
|
module Base
|
4
4
|
|
5
|
-
# Configure FriendlyId
|
6
|
-
# for your model.
|
5
|
+
# Configure FriendlyId's behavior in a model.
|
7
6
|
#
|
8
7
|
# class Post < ActiveRecord::Base
|
9
8
|
# extend FriendlyId
|
10
9
|
# friendly_id :title, :use => :slugged
|
11
10
|
# end
|
12
11
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
12
|
+
# When given the optional block, this method will yield the class's instance
|
13
|
+
# of {FriendlyId::Configuration} to the block before evaluating other
|
14
|
+
# arguments, so configuration values set in the block may be overwritten by
|
15
|
+
# the arguments. This order was chosen to allow passing the same proc to
|
16
|
+
# multiple models, while being able to override the values it sets. Here
|
17
|
+
# is a contrived example:
|
18
|
+
#
|
19
|
+
# $friendly_id_config_proc = Proc.new do |config|
|
20
|
+
# config.base = :name
|
21
|
+
# config.use :slugged
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Foo < ActiveRecord::Base
|
25
|
+
# extend FriendlyId
|
26
|
+
# friendly_id &$friendly_id_config_proc
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class Bar < ActiveRecord::Base
|
30
|
+
# extend FriendlyId
|
31
|
+
# friendly_id :title, &$friendly_id_config_proc
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# However, it's usually better to use {FriendlyId.defaults} for this:
|
35
|
+
#
|
36
|
+
# FriendlyId.defaults do |config|
|
37
|
+
# config.base = :name
|
38
|
+
# config.use :slugged
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class Foo < ActiveRecord::Base
|
42
|
+
# extend FriendlyId
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class Bar < ActiveRecord::Base
|
46
|
+
# extend FriendlyId
|
47
|
+
# friendly_id :title
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# In general you should use the block syntax either because of your personal
|
51
|
+
# aesthetic preference, or because you need to share some functionality
|
52
|
+
# between multiple models that can't be well encapsulated by
|
53
|
+
# {FriendlyId.defaults}.
|
54
|
+
#
|
55
|
+
# @option options [Symbol] :use The name of an addon to use. By default,
|
56
|
+
# FriendlyId provides {FriendlyId::Slugged :slugged},
|
57
|
+
# {FriendlyId::History :history}, {FriendlyId::Reserved :reserved}, and
|
58
|
+
# {FriendlyId::Scoped :scoped}.
|
59
|
+
#
|
60
|
+
# @option options [Array] :reserved_words Available when using +:reserved+,
|
61
|
+
# which is loaded by default. Sets an array of words banned for use as
|
62
|
+
# the basis of a friendly_id. By default this includes "edit" and "new".
|
63
|
+
#
|
64
|
+
# @option options [Symbol] :scope Available when using +:scoped+.
|
65
|
+
# Sets the relation or column used to scope generated friendly ids. This
|
66
|
+
# option has no default value.
|
67
|
+
#
|
68
|
+
# @option options [Symbol] :sequence_separator Available when using +:slugged+.
|
69
|
+
# Configures the sequence of characters used to separate a slug from a
|
70
|
+
# sequence. Defaults to +--+.
|
71
|
+
#
|
16
72
|
# @option options [Symbol] :slug_column Available when using +:slugged+.
|
17
73
|
# Configures the name of the column where FriendlyId will store the slug.
|
18
74
|
# Defaults to +:slug+.
|
75
|
+
#
|
76
|
+
# @option options [Symbol] :slug_sequencer_class Available when using +:slugged+.
|
77
|
+
# Sets the class used to generate unique slugs. You should not specify this
|
78
|
+
# unless you're doing some extensive hacking on FriendlyId. Defaults to
|
79
|
+
# {FriendlyId::SlugSequencer}.
|
80
|
+
#
|
81
|
+
# @yield Provides access to the model class's friendly_id_config, which
|
82
|
+
# allows an alternate configuration syntax, and conditional configuration
|
83
|
+
# logic.
|
84
|
+
#
|
85
|
+
# @yieldparam config The model class's {FriendlyId::Configuration friendly_id_config}.
|
19
86
|
def friendly_id(base = nil, options = {}, &block)
|
20
|
-
@friendly_id_config.use options.delete :use
|
21
|
-
@friendly_id_config.send :set, options.merge(:base => base)
|
22
87
|
yield @friendly_id_config if block_given?
|
88
|
+
@friendly_id_config.use options.delete :use
|
89
|
+
@friendly_id_config.send :set, base ? options.merge(:base => base) : options
|
23
90
|
before_save do |record|
|
24
91
|
record.instance_eval {@current_friendly_id = friendly_id}
|
25
92
|
end
|
26
93
|
include Model
|
27
94
|
end
|
28
95
|
|
96
|
+
# Returns the model class's {FriendlyId::Configuration friendly_id_config}.
|
29
97
|
def friendly_id_config
|
30
98
|
@friendly_id_config
|
31
99
|
end
|
@@ -25,39 +25,21 @@ module FriendlyId
|
|
25
25
|
# extend FriendlyId
|
26
26
|
# friendly_id :name
|
27
27
|
# end
|
28
|
-
|
28
|
+
attr_accessor :base
|
29
29
|
|
30
|
-
# The
|
31
|
-
# @return ActiveRecord::Base
|
32
|
-
attr_reader :klass
|
33
|
-
|
34
|
-
# The configuration parameters for the {#klass model class} using FriendlyId.
|
35
|
-
# @return Hash
|
30
|
+
# The default configuration options.
|
36
31
|
attr_reader :defaults
|
37
32
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
# The default configuration parameters for models using FriendlyId.
|
43
|
-
# @return Hash
|
44
|
-
def self.defaults
|
45
|
-
@@defaults
|
46
|
-
end
|
33
|
+
# The model class that this configuration belongs to.
|
34
|
+
# @return ActiveRecord::Base
|
35
|
+
attr_reader :model_class
|
47
36
|
|
48
|
-
def initialize(
|
49
|
-
@
|
50
|
-
@defaults
|
37
|
+
def initialize(model_class, values = nil)
|
38
|
+
@model_class = model_class
|
39
|
+
@defaults = {}
|
51
40
|
set values
|
52
41
|
end
|
53
42
|
|
54
|
-
def base=(base)
|
55
|
-
@base = base
|
56
|
-
if @base.respond_to?(:to_s)
|
57
|
-
@klass.validates_exclusion_of @base, :in => defaults[:reserved_words]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
43
|
# Lets you specify the modules to use with FriendlyId.
|
62
44
|
#
|
63
45
|
# This method is invoked by {FriendlyId::Base#friendly_id friendly_id} when
|
@@ -74,7 +56,7 @@ module FriendlyId
|
|
74
56
|
# default FriendlyId provides +:slugged+, +:history+ and +:scoped+.
|
75
57
|
def use(*modules)
|
76
58
|
modules.to_a.flatten.compact.map do |name|
|
77
|
-
|
59
|
+
model_class.send :include, FriendlyId.const_get(name.to_s.classify)
|
78
60
|
end
|
79
61
|
end
|
80
62
|
|
@@ -4,6 +4,14 @@ module FriendlyId
|
|
4
4
|
|
5
5
|
protected
|
6
6
|
|
7
|
+
# FriendlyId overrides this method to make it possible to use friendly id's
|
8
|
+
# identically to numeric ids in finders.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# person = Person.find(123)
|
12
|
+
# person = Person.find("joe")
|
13
|
+
#
|
14
|
+
# @see FriendlyId::ObjectUtils
|
7
15
|
def find_one(id)
|
8
16
|
return super if !@klass.respond_to?(:friendly_id) || id.unfriendly_id?
|
9
17
|
where(@klass.friendly_id_config.query_field => id).first or super
|
data/lib/friendly_id/history.rb
CHANGED
@@ -1,8 +1,40 @@
|
|
1
1
|
require "friendly_id/slug"
|
2
2
|
|
3
3
|
module FriendlyId
|
4
|
+
|
5
|
+
=begin
|
6
|
+
FriendlyId can maintain a history of your record's older slugs, so if your
|
7
|
+
record's friendly_id changes, your URL's won't break.
|
8
|
+
|
9
|
+
class Post < ActiveRecord::Base
|
10
|
+
extend FriendlyId
|
11
|
+
friendly_id :title, :use => :history
|
12
|
+
end
|
13
|
+
|
14
|
+
class PostsController < ApplicationController
|
15
|
+
|
16
|
+
before_filter :find_post
|
17
|
+
|
18
|
+
...
|
19
|
+
def find_post
|
20
|
+
return unless params[:id]
|
21
|
+
@post = begin
|
22
|
+
Post.find params[:id]
|
23
|
+
rescue ActiveRecord::RecordNotFound
|
24
|
+
Post.find_by_friendly_id params[:id]
|
25
|
+
end
|
26
|
+
# If an old id or a numeric id was used to find the record, then
|
27
|
+
# the request path will not match the post_path, and we should do
|
28
|
+
# a 301 redirect that uses the current friendly_id
|
29
|
+
if request.path != post_path(@post)
|
30
|
+
return redirect_to @post, :status => :moved_permanently
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
=end
|
4
35
|
module History
|
5
36
|
|
37
|
+
# Configures the model instance to use the History add-on.
|
6
38
|
def self.included(klass)
|
7
39
|
klass.instance_eval do
|
8
40
|
raise "FriendlyId::History is incompatibe with FriendlyId::Scoped" if self < Scoped
|
@@ -21,7 +53,10 @@ module FriendlyId
|
|
21
53
|
end
|
22
54
|
end
|
23
55
|
|
56
|
+
# Adds a finder that explictly uses slugs from the slug table.
|
24
57
|
module Finder
|
58
|
+
|
59
|
+
# Search for a record in the slugs table using the specified slug.
|
25
60
|
def find_by_friendly_id(*args)
|
26
61
|
with_friendly_id(args.shift).first(*args)
|
27
62
|
end
|