friendly_id 4.0.0 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +5 -5
- data/Changelog.md +11 -365
- data/Guide.rdoc +7 -6
- data/README.md +17 -11
- data/Rakefile +2 -2
- data/friendly_id.gemspec +11 -12
- data/gemfiles/Gemfile.rails-3.0.rb +15 -14
- data/gemfiles/Gemfile.rails-3.1.rb +16 -16
- data/gemfiles/Gemfile.rails-3.2.rb +16 -16
- data/lib/friendly_id.rb +2 -1
- data/lib/friendly_id/base.rb +1 -1
- data/lib/friendly_id/globalize.rb +102 -0
- data/lib/friendly_id/history.rb +24 -4
- data/lib/friendly_id/scoped.rb +13 -9
- data/lib/friendly_id/simple_i18n.rb +1 -1
- data/lib/friendly_id/slug_generator.rb +14 -6
- data/test/base_test.rb +4 -0
- data/test/configuration_test.rb +3 -1
- data/test/core_test.rb +3 -1
- data/test/generator_test.rb +0 -1
- data/test/helper.rb +0 -9
- data/test/history_test.rb +48 -3
- data/test/i18n_test.rb +1 -0
- data/test/object_utils_test.rb +4 -2
- data/test/schema.rb +3 -2
- data/test/scoped_test.rb +35 -9
- data/test/slugged_test.rb +7 -4
- data/test/sti_test.rb +11 -2
- metadata +43 -53
data/README.md
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
[![Build Status](http://travis-ci.org/norman/friendly_id.png)](http://travis-ci.org/norman/friendly_id)
|
4
4
|
|
5
5
|
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
|
6
|
-
Ruby on Rails. It allows you to create pretty
|
6
|
+
Ruby on Rails. It allows you to create pretty URLs and work with human-friendly
|
7
7
|
strings as if they were numeric ids for Active Record models.
|
8
8
|
|
9
|
-
Using FriendlyId, it's easy to make your application use
|
9
|
+
Using FriendlyId, it's easy to make your application use URLs like:
|
10
10
|
|
11
11
|
http://example.com/states/washington
|
12
12
|
|
@@ -18,9 +18,10 @@ instead of:
|
|
18
18
|
## FriendlyId Features
|
19
19
|
|
20
20
|
FriendlyId offers many advanced features, including: slug history and
|
21
|
-
versioning, i18n, scoped slugs, reserved words, and custom
|
21
|
+
versioning, i18n, Globalize support, scoped slugs, reserved words, and custom
|
22
|
+
slug generators.
|
22
23
|
|
23
|
-
FriendlyId is compatible with Active Record **3.0
|
24
|
+
FriendlyId is compatible with Active Record **3.0** and higher.
|
24
25
|
|
25
26
|
## Version 4.x
|
26
27
|
|
@@ -38,6 +39,8 @@ The best place to start is with the
|
|
38
39
|
[Guide](http://rubydoc.info/github/norman/friendly_id/master/file/Guide.rdoc),
|
39
40
|
which compiles the top-level RDocs into one outlined document.
|
40
41
|
|
42
|
+
You might also want to watch Ryan Bates's [Railscast on FriendlyId](http://railscasts.com/episodes/314-pretty-urls-with-friendlyid).
|
43
|
+
|
41
44
|
## Rails Quickstart
|
42
45
|
|
43
46
|
gem install friendly_id
|
@@ -46,22 +49,22 @@ which compiles the top-level RDocs into one outlined document.
|
|
46
49
|
|
47
50
|
cd my_app
|
48
51
|
|
49
|
-
gem "friendly_id", "~> 4.0.
|
52
|
+
gem "friendly_id", "~> 4.0.1"
|
50
53
|
|
51
54
|
rails generate scaffold user name:string slug:string
|
52
55
|
|
53
56
|
# edit db/migrate/*_create_users.rb
|
54
|
-
add_index :users, :slug, :
|
57
|
+
add_index :users, :slug, unique: true
|
55
58
|
|
56
59
|
rake db:migrate
|
57
60
|
|
58
61
|
# edit app/models/user.rb
|
59
62
|
class User < ActiveRecord::Base
|
60
63
|
extend FriendlyId
|
61
|
-
friendly_id :name, :
|
64
|
+
friendly_id :name, use: :slugged
|
62
65
|
end
|
63
66
|
|
64
|
-
User.create! :
|
67
|
+
User.create! name: "Joe Schmoe"
|
65
68
|
|
66
69
|
rails server
|
67
70
|
|
@@ -87,13 +90,16 @@ tracker](http://github.com/norman/friendly_id/issues) for this project.
|
|
87
90
|
If you have a bug to report, please include the following information:
|
88
91
|
|
89
92
|
* **Version information for FriendlyId, Rails and Ruby.**
|
90
|
-
*
|
93
|
+
* Full stack trace and error message (if you have them).
|
91
94
|
* Any snippets of relevant model, view or controller code that shows how you
|
92
95
|
are using FriendlyId.
|
93
96
|
|
94
97
|
If you are able to, it helps even more if you can fork FriendlyId on Github,
|
95
98
|
and add a test that reproduces the error you are experiencing.
|
96
99
|
|
100
|
+
For more info on how to report bugs, please see [this
|
101
|
+
article](http://yourbugreportneedsmore.info/).
|
102
|
+
|
97
103
|
## Thanks and Credits
|
98
104
|
|
99
105
|
FriendlyId was originally created by Norman Clarke and Adrian Mugnolo, with
|
@@ -105,7 +111,7 @@ Part of the inspiration to rework FriendlyId came from Darcy Laycock's library
|
|
105
111
|
[Slugged](https://github.com/Sutto/slugged), which he was inspired to create
|
106
112
|
because of frustrations he experienced while using FriendlyId 3.x. Seeing a
|
107
113
|
smart programmer become frustrated with my code was enough of a kick in the
|
108
|
-
butt to make me want to improve this library
|
114
|
+
butt to make me want to significantly improve this library.
|
109
115
|
|
110
116
|
Many thanks to him for providing valid, real criticism while still being a cool
|
111
117
|
about it. I definitely recommend you check out his library if for some reason
|
@@ -122,7 +128,7 @@ should be!
|
|
122
128
|
|
123
129
|
## License
|
124
130
|
|
125
|
-
Copyright (c) 2008-
|
131
|
+
Copyright (c) 2008-2012 Norman Clarke, released under the MIT license.
|
126
132
|
|
127
133
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
128
134
|
this software and associated documentation files (the "Software"), to deal in
|
data/Rakefile
CHANGED
@@ -68,7 +68,7 @@ namespace :db do
|
|
68
68
|
driver = FriendlyId::Test::Database.driver
|
69
69
|
config = FriendlyId::Test::Database.config[driver]
|
70
70
|
commands = {
|
71
|
-
"mysql" => "mysql -e 'create database #{config["database"]};' >/dev/null",
|
71
|
+
"mysql" => "mysql -u #{config['username']} -e 'create database #{config["database"]};' >/dev/null",
|
72
72
|
"postgres" => "psql -c 'create database #{config['database']};' -U #{config['username']} >/dev/null"
|
73
73
|
}
|
74
74
|
%x{#{commands[driver] || true}}
|
@@ -80,7 +80,7 @@ namespace :db do
|
|
80
80
|
driver = FriendlyId::Test::Database.driver
|
81
81
|
config = FriendlyId::Test::Database.config[driver]
|
82
82
|
commands = {
|
83
|
-
"mysql" => "mysql -e 'drop database #{config["database"]};' >/dev/null",
|
83
|
+
"mysql" => "mysql -u #{config['username']} -e 'drop database #{config["database"]};' >/dev/null",
|
84
84
|
"postgres" => "psql -c 'drop database #{config['database']};' -U #{config['username']} >/dev/null"
|
85
85
|
}
|
86
86
|
%x{#{commands[driver] || true}}
|
data/friendly_id.gemspec
CHANGED
@@ -8,28 +8,27 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.version = FriendlyId::VERSION
|
9
9
|
s.authors = ["Norman Clarke"]
|
10
10
|
s.email = ["norman@njclarke.com"]
|
11
|
-
s.homepage = "http://
|
11
|
+
s.homepage = "http://github.com/norman/friendly_id"
|
12
12
|
s.summary = "A comprehensive slugging and pretty-URL plugin."
|
13
13
|
s.rubyforge_project = "friendly_id"
|
14
14
|
s.files = `git ls-files`.split("\n")
|
15
15
|
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
|
18
|
-
s.add_development_dependency "railties", "~> 3.
|
19
|
-
s.add_development_dependency "activerecord", "~> 3.
|
20
|
-
s.add_development_dependency "
|
21
|
-
s.add_development_dependency "
|
22
|
-
s.add_development_dependency "
|
23
|
-
s.add_development_dependency "
|
24
|
-
s.add_development_dependency "
|
25
|
-
s.add_development_dependency "
|
26
|
-
s.add_development_dependency "i18n", "~> 0.6.0"
|
18
|
+
s.add_development_dependency "railties", "~> 3.2.0"
|
19
|
+
s.add_development_dependency "activerecord", "~> 3.2.0"
|
20
|
+
s.add_development_dependency "minitest"
|
21
|
+
s.add_development_dependency "mocha"
|
22
|
+
s.add_development_dependency "maruku"
|
23
|
+
s.add_development_dependency "yard"
|
24
|
+
s.add_development_dependency "i18n"
|
25
|
+
s.add_development_dependency "ffaker"
|
27
26
|
s.add_development_dependency "simplecov"
|
28
27
|
|
29
28
|
s.description = <<-EOM
|
30
29
|
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for
|
31
|
-
Ruby on Rails. It allows you to create pretty
|
32
|
-
|
30
|
+
Ruby on Rails. It allows you to create pretty URLs and work with human-friendly
|
31
|
+
strings as if they were numeric ids for Active Record models.
|
33
32
|
EOM
|
34
33
|
|
35
34
|
s.post_install_message = <<-EOM
|
@@ -1,19 +1,20 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
platforms :jruby do
|
4
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
5
|
+
gem 'activerecord-jdbcmysql-adapter'
|
6
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
7
|
+
gem 'jruby-openssl'
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
10
|
+
platforms :ruby do
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'mysql2', '~> 0.2.0'
|
13
|
+
gem 'pg'
|
13
14
|
end
|
14
15
|
|
15
|
-
gem
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem
|
19
|
-
gem
|
16
|
+
gem 'activerecord', '~> 3.0.0'
|
17
|
+
gem 'railties', '~> 3.0.0'
|
18
|
+
gem 'minitest'
|
19
|
+
gem 'mocha'
|
20
|
+
gem 'rake'
|
@@ -1,21 +1,21 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
platforms :jruby do
|
4
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
5
|
+
gem 'activerecord-jdbcmysql-adapter'
|
6
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
7
|
+
gem 'jruby-openssl'
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
10
|
+
platforms :ruby do
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'mysql2'
|
13
|
+
gem 'pg'
|
14
14
|
end
|
15
15
|
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem
|
19
|
-
gem
|
20
|
-
gem
|
21
|
-
gem
|
16
|
+
gem 'activerecord', '~> 3.1.0'
|
17
|
+
gem 'railties', '~> 3.1.3'
|
18
|
+
gem 'ffaker'
|
19
|
+
gem 'minitest'
|
20
|
+
gem 'mocha'
|
21
|
+
gem 'rake'
|
@@ -1,21 +1,21 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
platforms :jruby do
|
4
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
5
|
+
gem 'activerecord-jdbcmysql-adapter'
|
6
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
7
|
+
gem 'jruby-openssl'
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
10
|
+
platforms :ruby do
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'mysql2'
|
13
|
+
gem 'pg'
|
14
14
|
end
|
15
15
|
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem
|
19
|
-
gem
|
20
|
-
gem
|
21
|
-
gem
|
16
|
+
gem 'ffaker'
|
17
|
+
gem 'activerecord', '~> 3.2.0'
|
18
|
+
gem 'railties', '~> 3.2.0'
|
19
|
+
gem 'minitest'
|
20
|
+
gem 'mocha'
|
21
|
+
gem 'rake'
|
data/lib/friendly_id.rb
CHANGED
@@ -45,7 +45,7 @@ with numeric ids:
|
|
45
45
|
module FriendlyId
|
46
46
|
|
47
47
|
# The current version.
|
48
|
-
VERSION = "4.0.
|
48
|
+
VERSION = "4.0.1"
|
49
49
|
|
50
50
|
@mutex = Mutex.new
|
51
51
|
|
@@ -54,6 +54,7 @@ module FriendlyId
|
|
54
54
|
autoload :Reserved, "friendly_id/reserved"
|
55
55
|
autoload :Scoped, "friendly_id/scoped"
|
56
56
|
autoload :Slugged, "friendly_id/slugged"
|
57
|
+
autoload :Globalize, "friendly_id/globalize"
|
57
58
|
|
58
59
|
# FriendlyId takes advantage of `extended` to do basic model setup, primarily
|
59
60
|
# extending {FriendlyId::Base} to add {FriendlyId::Base#friendly_id
|
data/lib/friendly_id/base.rb
CHANGED
@@ -9,7 +9,7 @@ method to configure your desired options:
|
|
9
9
|
|
10
10
|
class Foo < ActiveRecord::Base
|
11
11
|
extend FriendlyId
|
12
|
-
friendly_id bar, :use => [:slugged, :simple_i18n]
|
12
|
+
friendly_id :bar, :use => [:slugged, :simple_i18n]
|
13
13
|
end
|
14
14
|
|
15
15
|
The most important option is `:use`, which you use to tell FriendlyId which
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
|
3
|
+
module FriendlyId
|
4
|
+
|
5
|
+
=begin
|
6
|
+
|
7
|
+
== Translate slug db column using Globalize
|
8
|
+
|
9
|
+
The {FriendlyId::Globalize Globalize} module allow to use
|
10
|
+
Globalize (https://github.com/svenfuchs/globalize3) to translate slugs.
|
11
|
+
|
12
|
+
In order to use this module, your model must have a slug column and set the
|
13
|
+
field +slug+ translable with Globalize:
|
14
|
+
|
15
|
+
class Post < ActiveRecord::Base
|
16
|
+
translates :title, :slug
|
17
|
+
extend FriendlyId
|
18
|
+
friendly_id :title, :use => :globalize
|
19
|
+
end
|
20
|
+
|
21
|
+
=== Finds
|
22
|
+
|
23
|
+
Finds will take into consideration the current locale:
|
24
|
+
|
25
|
+
I18n.locale = :it
|
26
|
+
Post.find("guerre-stellari")
|
27
|
+
I18n.locale = :en
|
28
|
+
Post.find("star-wars")
|
29
|
+
|
30
|
+
To find a slug by an explicit locale, perform the find inside a block
|
31
|
+
passed to I18n's +with_locale+ method:
|
32
|
+
|
33
|
+
I18n.with_locale(:it) do
|
34
|
+
Post.find("guerre-stellari")
|
35
|
+
end
|
36
|
+
|
37
|
+
=== Creating Records
|
38
|
+
|
39
|
+
When new records are created, the slug is generated for the current locale only.
|
40
|
+
|
41
|
+
=== Translating Slugs
|
42
|
+
|
43
|
+
To translate an existing record's friendly_id, simply change locale and assign
|
44
|
+
+slug+ field:
|
45
|
+
|
46
|
+
I18n.with_locale(:it) do
|
47
|
+
post.slug = "guerre-stellari"
|
48
|
+
end
|
49
|
+
|
50
|
+
=end
|
51
|
+
module Globalize
|
52
|
+
|
53
|
+
def self.included(model_class)
|
54
|
+
model_class.instance_eval do
|
55
|
+
friendly_id_config.use :slugged
|
56
|
+
relation_class.send :include, FinderMethods
|
57
|
+
include Model
|
58
|
+
# Check if slug field is enabled to be translated with Globalize
|
59
|
+
if table_exists?
|
60
|
+
if columns.map(&:name).exclude?(friendly_id_config.query_field)
|
61
|
+
puts "\n[FriendlyId] Missing field '#{friendly_id_config.query_field}' in DB table '#{table_name}'. This is required for FriendlyId to properly function with the :globalize option.\n\n"
|
62
|
+
end
|
63
|
+
unless respond_to?('translated_attribute_names') || translated_attribute_names.exclude?(friendly_id_config.query_field.to_sym)
|
64
|
+
puts "\n[FriendlyId] You need to translate '#{friendly_id_config.query_field}' field with Globalize (add 'translates :#{friendly_id_config.query_field}' in your model '#{self.class.name}')\n\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
module Model
|
71
|
+
def slug=(text)
|
72
|
+
set_slug(normalize_friendly_id(text))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module FinderMethods
|
77
|
+
# FriendlyId overrides this method to make it possible to use friendly id's
|
78
|
+
# identically to numeric ids in finders.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# person = Person.find(123)
|
82
|
+
# person = Person.find("joe")
|
83
|
+
#
|
84
|
+
# @see FriendlyId::ObjectUtils
|
85
|
+
def find_one(id)
|
86
|
+
return super if id.unfriendly_id?
|
87
|
+
where(@klass.friendly_id_config.query_field => id).first or
|
88
|
+
joins(:translations).
|
89
|
+
where(translation_class.arel_table[:locale].eq(I18n.locale)).
|
90
|
+
where(translation_class.arel_table[@klass.friendly_id_config.query_field].eq(id)).first or
|
91
|
+
# if locale is not translated fallback to default locale
|
92
|
+
joins(:translations).
|
93
|
+
where(translation_class.arel_table[:locale].eq(I18n.default_locale)).
|
94
|
+
where(translation_class.arel_table[@klass.friendly_id_config.query_field].eq(id)).first or
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
protected :find_one
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/friendly_id/history.rb
CHANGED
@@ -65,20 +65,21 @@ method.
|
|
65
65
|
@friendly_id_config.use :slugged
|
66
66
|
has_many :slugs, :as => :sluggable, :dependent => :destroy,
|
67
67
|
:class_name => Slug.to_s, :order => "#{Slug.quoted_table_name}.id DESC"
|
68
|
-
|
68
|
+
after_save :create_slug
|
69
69
|
relation_class.send :include, FinderMethods
|
70
|
+
friendly_id_config.slug_generator_class.send :include, SlugGenerator
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
73
74
|
private
|
74
75
|
|
75
|
-
def
|
76
|
-
return
|
76
|
+
def create_slug
|
77
|
+
return if slugs.first.try(:slug) == friendly_id
|
77
78
|
# Allow reversion back to a previously used slug
|
78
79
|
relation = slugs.where(:slug => friendly_id)
|
79
80
|
result = relation.select("id").lock(true).all
|
80
81
|
relation.delete_all unless result.empty?
|
81
|
-
slugs.
|
82
|
+
slugs.create! :slug => friendly_id
|
82
83
|
end
|
83
84
|
|
84
85
|
# Adds a finder that explictly uses slugs from the slug table.
|
@@ -110,5 +111,24 @@ method.
|
|
110
111
|
yield sluggable_id if sluggable_id
|
111
112
|
end
|
112
113
|
end
|
114
|
+
|
115
|
+
# This module overrides {FriendlyId::SlugGenerator#conflicts} to consider
|
116
|
+
# all historic slugs for that model.
|
117
|
+
module SlugGenerator
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def conflicts
|
122
|
+
sluggable_class = friendly_id_config.model_class
|
123
|
+
pkey = sluggable_class.primary_key
|
124
|
+
value = sluggable.send pkey
|
125
|
+
# TODO this is a bit of a performance drain right now; optimize before next release.
|
126
|
+
scope = sluggable_class.unscoped.includes(:slugs).where("#{Slug.quoted_table_name}.slug = ? OR #{Slug.quoted_table_name}.slug LIKE ?", normalized, wildcard)
|
127
|
+
scope = scope.where(Slug.table_name => {:sluggable_type => sluggable_class.name})
|
128
|
+
scope = scope.where("#{sluggable_class.table_name}.#{pkey} <> ?", value) unless sluggable.new_record?
|
129
|
+
scope.order("LENGTH(#{Slug.quoted_table_name}.slug) DESC, #{Slug.quoted_table_name}.slug DESC")
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
113
133
|
end
|
114
134
|
end
|