friendly_id4 4.0.0.beta6 → 4.0.0.pre
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/README.md +47 -68
- data/Rakefile +13 -108
- data/lib/friendly_id.rb +97 -117
- data/lib/friendly_id/scoped.rb +17 -116
- data/lib/friendly_id/slugged.rb +89 -183
- data/lib/friendly_id/test.rb +23 -0
- data/lib/friendly_id/test/generic.rb +84 -0
- data/lib/friendly_id/version.rb +9 -0
- data/test/core_test.rb +54 -16
- data/test/scoped_test.rb +39 -35
- data/test/slugged_test.rb +45 -64
- data/test/test_helper.rb +23 -0
- metadata +61 -125
- data/.gemtest +0 -0
- data/.gitignore +0 -11
- data/.yardopts +0 -4
- data/WhatsNew.md +0 -142
- data/bench.rb +0 -63
- data/friendly_id.gemspec +0 -31
- data/lib/friendly_id/base.rb +0 -134
- data/lib/friendly_id/configuration.rb +0 -78
- data/lib/friendly_id/finder_methods.rb +0 -20
- data/lib/friendly_id/history.rb +0 -64
- data/lib/friendly_id/migration.rb +0 -18
- data/lib/friendly_id/model.rb +0 -22
- data/lib/friendly_id/object_utils.rb +0 -40
- data/lib/friendly_id/reserved.rb +0 -46
- data/lib/friendly_id/slug.rb +0 -6
- data/lib/friendly_id/slug_sequencer.rb +0 -82
- data/lib/generators/friendly_id_generator.rb +0 -24
- data/test/base_test.rb +0 -54
- data/test/config/mysql.yml +0 -5
- data/test/config/mysql2.yml +0 -5
- data/test/config/postgres.yml +0 -6
- data/test/config/sqlite3.yml +0 -3
- data/test/configuration_test.rb +0 -27
- data/test/helper.rb +0 -90
- data/test/history_test.rb +0 -55
- data/test/object_utils_test.rb +0 -26
- data/test/reserved_test.rb +0 -26
- data/test/schema.rb +0 -56
- data/test/shared.rb +0 -118
- data/test/sti_test.rb +0 -48
data/README.md
CHANGED
@@ -1,95 +1,74 @@
|
|
1
|
-
# FriendlyId
|
1
|
+
# FriendlyId 4
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
This is an in-progress rethink of the FriendlyId plugin. It will probably be
|
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
6
|
|
7
|
-
|
7
|
+
Please don't use this yet for anything real but feel free to try it and give
|
8
|
+
your feedback via the issues tracker.
|
8
9
|
|
9
|
-
|
10
|
+
## Back to basics
|
10
11
|
|
11
|
-
|
12
|
+
This isn't the "big rewrite," it's the "small rewrite."
|
12
13
|
|
13
|
-
|
14
|
+
Adding new features with each release is not sustainable. This release *removes*
|
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.
|
14
18
|
|
19
|
+
Let's see how small we can make this!
|
15
20
|
|
16
|
-
|
21
|
+
Here's what's changed:
|
17
22
|
|
18
|
-
|
19
|
-
versioning, scoped slugs, reserved words, and custom slug generators.
|
23
|
+
## Active Record 3+ only
|
20
24
|
|
21
|
-
|
25
|
+
For 2.3 support, you can use FriendlyId 3, which will continue to be maintained
|
26
|
+
until people don't want it any more.
|
22
27
|
|
23
|
-
##
|
28
|
+
## Remove Babosa
|
24
29
|
|
25
|
-
|
30
|
+
Babosa is FriendlyId 3's slugging library.
|
26
31
|
|
27
|
-
|
32
|
+
FriendlyId 4 doesn't use it by default any more because the most important
|
33
|
+
pieces of it were already accepted into Active Support 3. You can still just
|
34
|
+
override `#normalize_friendly_id` in your model if you want to use Babosa.
|
28
35
|
|
29
|
-
|
36
|
+
## In-table slugs
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
FriendlyId no longer creates a separate slugs table - it just stores the
|
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).
|
34
42
|
|
43
|
+
## No more finder status
|
35
44
|
|
36
|
-
|
45
|
+
FriendlyId 3 offered finder statuses to help you determine when an outdated
|
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:
|
37
49
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
rake db:migrate
|
42
|
-
|
43
|
-
# edit app/models/user.rb
|
44
|
-
class User < ActiveRecord::Base
|
45
|
-
extend FriendlyId
|
46
|
-
friendly_id :name, :use => :slugged
|
50
|
+
if request.path != person_path(@person)
|
51
|
+
return redirect_to @person, :status => :moved_permanently
|
47
52
|
end
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
rails server
|
52
|
-
|
53
|
-
GET http://localhost:3000/users/joe-schmoe
|
54
|
-
|
55
|
-
## Docs
|
56
|
-
|
57
|
-
The current docs can be found
|
58
|
-
[here](http://rdoc.info/github/norman/friendly_id/a4128af31d85ee29ad8f/frames).
|
59
|
-
|
60
|
-
## Future Compatibility
|
61
|
-
|
62
|
-
FriendlyId will always remain compatible with the current release of Rails, and
|
63
|
-
at least one stable release behind. That means that support for 3.0.x will not be
|
64
|
-
dropped until a stable release of 3.2 is out, or possibly longer.
|
65
|
-
|
66
|
-
|
67
|
-
## Benchmarks
|
68
|
-
|
69
|
-
The latest benchmarks for FriendlyId are maintained
|
70
|
-
[here](https://gist.github.com/1129745).
|
71
|
-
|
54
|
+
## No more multiple finds
|
72
55
|
|
73
|
-
|
56
|
+
Person.find "joe-schmoe" # Supported
|
57
|
+
Person.find ["joe-schmoe", "john-doe"] # No longer supported
|
74
58
|
|
75
|
-
|
76
|
-
tracker](http://github.com/norman/friendly_id/issues) for this project.
|
59
|
+
If you want find by more than one friendly id, build your own query:
|
77
60
|
|
78
|
-
|
61
|
+
Person.where(:slug => ["joe-schmoe", "john-doe"])
|
79
62
|
|
80
|
-
|
81
|
-
* Stack trace and error message.
|
82
|
-
* Any snippets of relevant model, view or controller code that shows how you
|
83
|
-
are using FriendlyId.
|
63
|
+
This lets us do *far* less monkeypatching in Active Record.
|
84
64
|
|
85
|
-
|
86
|
-
and add a test that reproduces the error you are experiencing.
|
65
|
+
## No more reserved words
|
87
66
|
|
88
|
-
|
67
|
+
Rather than use a custom reserved words validator, use the validations provided
|
68
|
+
by Active Record. FriendlyId still reserves "new" and "edit" by default to avoid
|
69
|
+
routing problems.
|
89
70
|
|
90
|
-
|
91
|
-
significant help early in its life by Emilio Tagua. I'm deeply gratful for the
|
92
|
-
generous contributions over the years from [many
|
93
|
-
volunteers](https://github.com/norman/friendly_id/contributors).
|
71
|
+
validates_exclusion_of :name, :in => ["bad", "word"]
|
94
72
|
|
95
|
-
|
73
|
+
You can configure the default words reserved by FriendlyId in
|
74
|
+
`FriendlyId::Configuration::DEFAULTS[:reserved_words]`.
|
data/Rakefile
CHANGED
@@ -1,119 +1,24 @@
|
|
1
|
-
require "
|
1
|
+
require "rake"
|
2
2
|
require "rake/testtask"
|
3
|
-
|
4
|
-
|
5
|
-
["ruby-1.9.2-p180", "ree-1.8.7-2011.03", "jruby-1.6.2", "rbx-2.0.0pre"].each do |ruby|
|
6
|
-
old = ENV["RB"]
|
7
|
-
ENV["RB"] = ruby
|
8
|
-
yield
|
9
|
-
ENV["RB"] = old
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def versions(&block)
|
14
|
-
["3.1.0.rc5", "3.0.9"].each do |version|
|
15
|
-
old = ENV["AR"]
|
16
|
-
ENV["AR"] = version
|
17
|
-
yield
|
18
|
-
ENV["AR"] = old
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def adapters(&block)
|
23
|
-
["mysql", "mysql2", "postgres", "sqlite3"].each do |adapter|
|
24
|
-
old = ENV["DB"]
|
25
|
-
ENV["DB"] = adapter
|
26
|
-
ENV["DB_VERSION"] = "~> 0.3.6" if adapter == "mysql2" && ENV["AR"] && ENV["AR"][0..2] >= "3.1"
|
27
|
-
yield
|
28
|
-
ENV["DB"] = old
|
29
|
-
end
|
30
|
-
end
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "rake/clean"
|
31
5
|
|
32
6
|
task :default => :test
|
33
7
|
|
34
|
-
|
35
|
-
t.test_files = FileList['test/*_test.rb']
|
36
|
-
t.verbose = true
|
37
|
-
end
|
38
|
-
|
39
|
-
task :clean do
|
40
|
-
%x{rm -rf *.gem doc pkg coverage}
|
41
|
-
%x{rm -f `find . -name '*.rbc'`}
|
42
|
-
end
|
43
|
-
|
44
|
-
task :gem do
|
45
|
-
%x{gem build friendly_id.gemspec}
|
46
|
-
end
|
47
|
-
|
48
|
-
task :yard do
|
49
|
-
puts %x{bundle exec yard}
|
50
|
-
end
|
51
|
-
|
52
|
-
task :bench do
|
53
|
-
require File.expand_path("../bench", __FILE__)
|
54
|
-
end
|
55
|
-
|
56
|
-
desc "Bundle for all supported Ruby/AR versions"
|
57
|
-
task :bundle do
|
58
|
-
rubies do
|
59
|
-
versions do
|
60
|
-
command = "#{ENV["RB"]} -S bundle"
|
61
|
-
puts "#{command} (with #{ENV['AR']})"
|
62
|
-
`#{command}`
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
namespace :test do
|
68
|
-
|
69
|
-
desc "Test with all configured adapters"
|
70
|
-
task :adapters do
|
71
|
-
adapters {|a| puts %x{rake test}}
|
72
|
-
end
|
73
|
-
|
74
|
-
desc "Test with all configured Rubies"
|
75
|
-
task :rubies do
|
76
|
-
rubies {|r| puts %x{rake-#{ENV["RB"]} test}}
|
77
|
-
end
|
78
|
-
|
79
|
-
desc "Test with all configured versions"
|
80
|
-
task :versions do
|
81
|
-
versions {|v| puts %x{rake test}}
|
82
|
-
end
|
8
|
+
CLEAN << "pkg" << "doc" << "coverage" << ".yardoc"
|
83
9
|
|
84
|
-
|
85
|
-
task :prerelease do
|
86
|
-
rubies do
|
87
|
-
versions do
|
88
|
-
adapters do
|
89
|
-
puts %x{rake test}
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
10
|
+
Rake::TestTask.new(:test) { |t| t.pattern = "test/**/*test.rb" }
|
94
11
|
|
95
|
-
|
96
|
-
task
|
97
|
-
|
98
|
-
|
99
|
-
puts "Running #{test}:"
|
100
|
-
puts %x{ruby #{test}}
|
101
|
-
end
|
12
|
+
begin
|
13
|
+
require 'reek/rake/task'
|
14
|
+
Reek::Rake::Task.new do |t|
|
15
|
+
t.fail_on_error = false
|
102
16
|
end
|
17
|
+
rescue LoadError
|
103
18
|
end
|
104
19
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
require File.expand_path("../test/helper", __FILE__)
|
109
|
-
FriendlyId::Test::Schema.up
|
110
|
-
end
|
111
|
-
|
112
|
-
desc "Destroy the database schema"
|
113
|
-
task :down do
|
114
|
-
require File.expand_path("../test/helper", __FILE__)
|
115
|
-
FriendlyId::Test::Schema.down
|
116
|
-
end
|
20
|
+
gemspec = File.expand_path("../friendly_id.gemspec", __FILE__)
|
21
|
+
if File.exists? gemspec
|
22
|
+
Rake::GemPackageTask.new(eval(File.read("friendly_id.gemspec"))) { |pkg| }
|
117
23
|
end
|
118
24
|
|
119
|
-
task :doc => :yard
|
data/lib/friendly_id.rb
CHANGED
@@ -1,141 +1,121 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require "friendly_id/object_utils"
|
6
|
-
require "friendly_id/configuration"
|
7
|
-
require "friendly_id/finder_methods"
|
8
|
-
|
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
|
1
|
+
# FriendlyId is a comprehensive Ruby library for ActiveRecord permalinks and
|
2
|
+
# slugs.
|
3
|
+
# @author Norman Clarke
|
4
|
+
module FriendlyId
|
18
5
|
|
19
|
-
|
20
|
-
|
6
|
+
autoload :Slugged, "friendly_id/slugged"
|
7
|
+
autoload :Scoped, "friendly_id/scoped"
|
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
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
def friendly_id_config
|
26
|
+
@friendly_id_config ||= Configuration.new(self)
|
27
|
+
end
|
24
28
|
|
25
|
-
|
29
|
+
def uses_friendly_id?
|
30
|
+
!! @friendly_id_config
|
31
|
+
end
|
32
|
+
end
|
26
33
|
|
27
|
-
|
28
|
-
|
34
|
+
# Instance methods that will be added to all classes using FriendlyId.
|
35
|
+
module Model
|
29
36
|
|
30
|
-
|
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
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
42
|
+
# Get the instance's friendly_id.
|
43
|
+
def friendly_id
|
44
|
+
send friendly_id_config.query_field
|
45
|
+
end
|
35
46
|
|
36
|
-
|
37
|
-
|
38
|
-
friendly_id
|
39
|
-
validates_format_of :login, :with => /\A[a-z0-9]+\z/i
|
47
|
+
# Either the friendly_id, or the numeric id cast to a string.
|
48
|
+
def to_param
|
49
|
+
(friendly_id or id).to_s
|
40
50
|
end
|
51
|
+
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
45
58
|
|
46
|
-
|
47
|
-
|
48
|
-
|
59
|
+
DEFAULTS = {
|
60
|
+
:config_error_message => 'FriendlyId has no such config option "%s"',
|
61
|
+
:reserved_words => ["new", "edit"]
|
62
|
+
}
|
49
63
|
|
50
|
-
|
51
|
-
|
52
|
-
|
64
|
+
def initialize(klass, values = nil)
|
65
|
+
@klass = klass
|
66
|
+
set values
|
53
67
|
end
|
54
68
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
column.
|
69
|
+
def method_missing(symbol, *args, &block)
|
70
|
+
option = symbol.to_s.gsub(/=\z/, '')
|
71
|
+
raise ArgumentError, DEFAULTS[:config_error_message] % option
|
72
|
+
end
|
60
73
|
|
61
|
-
|
74
|
+
def set(values)
|
75
|
+
values and values.each {|name, value| self.send "#{name}=", value}
|
76
|
+
end
|
62
77
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
def query_field
|
79
|
+
base
|
80
|
+
end
|
81
|
+
end
|
67
82
|
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
71
96
|
end
|
72
97
|
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
76
104
|
|
77
|
-
|
105
|
+
# These methods will override the finder methods in ActiveRecord::Relation.
|
106
|
+
module FinderMethods
|
78
107
|
|
79
|
-
|
80
|
-
=end
|
81
|
-
module FriendlyId
|
108
|
+
protected
|
82
109
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
autoload :History, "friendly_id/history"
|
89
|
-
autoload :Reserved, "friendly_id/reserved"
|
90
|
-
autoload :Scoped, "friendly_id/scoped"
|
91
|
-
autoload :Slugged, "friendly_id/slugged"
|
92
|
-
|
93
|
-
# FriendlyId takes advantage of `extended` to do basic model setup, primarily
|
94
|
-
# extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
|
95
|
-
# friendly_id} as a class method.
|
96
|
-
#
|
97
|
-
# Previous versions of FriendlyId simply patched ActiveRecord::Base, but this
|
98
|
-
# version tries to be less invasive.
|
99
|
-
#
|
100
|
-
# In addition to adding {FriendlyId::Base#friendly_id friendly_id}, the class
|
101
|
-
# instance variable +@friendly_id_config+ is added. This variable is an
|
102
|
-
# instance of an anonymous subclass of {FriendlyId::Configuration}. This
|
103
|
-
# allows subsequently loaded modules like {FriendlyId::Slugged} and
|
104
|
-
# {FriendlyId::Scoped} to add functionality to the configuration class only
|
105
|
-
# for the current class, rather than monkey patching
|
106
|
-
# {FriendlyId::Configuration} directly. This isolates other models from large
|
107
|
-
# feature changes an addon to FriendlyId could potentially introduce.
|
108
|
-
#
|
109
|
-
# The upshot of this is, you can htwo Active Record models that both have a
|
110
|
-
# @friendly_id_config, but each config object can have different methods and
|
111
|
-
# behaviors depending on what modules have been loaded, without conflicts.
|
112
|
-
# Keep this in mind if you're hacking on FriendlyId.
|
113
|
-
#
|
114
|
-
# For examples of this, see the source for {Scoped.included}.
|
115
|
-
def self.extended(base)
|
116
|
-
base.instance_eval do
|
117
|
-
extend FriendlyId::Base
|
118
|
-
@friendly_id_config = Class.new(FriendlyId::Configuration).new(base)
|
119
|
-
if defaults = FriendlyId.defaults
|
120
|
-
defaults.yield @friendly_id_config
|
121
|
-
end
|
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
|
122
114
|
end
|
123
|
-
ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
|
124
|
-
end
|
125
115
|
|
126
|
-
# Set global defaults for all models using FriendlyId.
|
127
|
-
#
|
128
|
-
# The default defaults are to use the +:reserved+ module and nothing else.
|
129
|
-
#
|
130
|
-
# @example
|
131
|
-
# FriendlyId.defaults do |config|
|
132
|
-
# config.base = :name
|
133
|
-
# config.use :slugged
|
134
|
-
# end
|
135
|
-
def self.defaults(&block)
|
136
|
-
@mutex.synchronize do
|
137
|
-
@defaults = block if block_given?
|
138
|
-
@defaults ||= lambda {|config| config.use :reserved}
|
139
|
-
end
|
140
116
|
end
|
141
|
-
end
|
117
|
+
end
|
118
|
+
|
119
|
+
ActiveRecord::Base.extend FriendlyId::Base
|
120
|
+
ActiveRecord::Relation.send :include, FriendlyId::FinderMethods
|
121
|
+
Object.send :include, FriendlyId::ObjectUtils
|