friendly_id 5.4.0 → 5.5.1
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
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/stale.yml +1 -1
- data/.github/workflows/test.yml +38 -36
- data/.yardopts +2 -0
- data/Changelog.md +19 -0
- data/Gemfile +9 -13
- data/README.md +31 -8
- data/Rakefile +24 -27
- data/bench.rb +30 -27
- data/certs/parndt.pem +25 -23
- data/friendly_id.gemspec +26 -29
- data/gemfiles/Gemfile.rails-5.2.rb +11 -16
- data/gemfiles/Gemfile.rails-6.0.rb +11 -16
- data/gemfiles/Gemfile.rails-6.1.rb +22 -0
- data/gemfiles/Gemfile.rails-7.0.rb +22 -0
- data/guide.rb +13 -6
- data/lib/friendly_id/base.rb +59 -60
- data/lib/friendly_id/candidates.rb +9 -11
- data/lib/friendly_id/configuration.rb +6 -7
- data/lib/friendly_id/finder_methods.rb +63 -15
- data/lib/friendly_id/finders.rb +66 -66
- data/lib/friendly_id/history.rb +62 -63
- data/lib/friendly_id/initializer.rb +4 -4
- data/lib/friendly_id/migration.rb +6 -6
- data/lib/friendly_id/object_utils.rb +2 -2
- data/lib/friendly_id/reserved.rb +30 -32
- data/lib/friendly_id/scoped.rb +99 -102
- data/lib/friendly_id/sequentially_slugged/calculator.rb +69 -0
- data/lib/friendly_id/sequentially_slugged.rb +17 -64
- data/lib/friendly_id/simple_i18n.rb +78 -69
- data/lib/friendly_id/slug.rb +1 -2
- data/lib/friendly_id/slug_generator.rb +1 -3
- data/lib/friendly_id/slugged.rb +238 -239
- data/lib/friendly_id/version.rb +1 -1
- data/lib/friendly_id.rb +47 -49
- data/lib/generators/friendly_id_generator.rb +9 -9
- data/test/base_test.rb +10 -13
- data/test/benchmarks/finders.rb +28 -26
- data/test/benchmarks/object_utils.rb +13 -13
- data/test/candidates_test.rb +17 -18
- data/test/configuration_test.rb +7 -11
- data/test/core_test.rb +1 -2
- data/test/databases.yml +4 -3
- data/test/finders_test.rb +36 -13
- data/test/generator_test.rb +16 -26
- data/test/helper.rb +31 -24
- data/test/history_test.rb +70 -74
- data/test/numeric_slug_test.rb +4 -4
- data/test/object_utils_test.rb +0 -2
- data/test/reserved_test.rb +9 -11
- data/test/schema.rb +5 -4
- data/test/scoped_test.rb +18 -20
- data/test/sequentially_slugged_test.rb +65 -50
- data/test/shared.rb +15 -16
- data/test/simple_i18n_test.rb +22 -12
- data/test/slugged_test.rb +125 -113
- data/test/sti_test.rb +19 -21
- data.tar.gz.sig +0 -0
- metadata +38 -34
- metadata.gz.sig +0 -0
- data/gemfiles/Gemfile.rails-5.0.rb +0 -28
- data/gemfiles/Gemfile.rails-5.1.rb +0 -27
data/lib/friendly_id/finders.rb
CHANGED
@@ -1,73 +1,73 @@
|
|
1
1
|
module FriendlyId
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
2
|
+
# @guide begin
|
3
|
+
#
|
4
|
+
# ## Performing Finds with FriendlyId
|
5
|
+
#
|
6
|
+
# FriendlyId offers enhanced finders which will search for your record by
|
7
|
+
# friendly id, and fall back to the numeric id if necessary. This makes it easy
|
8
|
+
# to add FriendlyId to an existing application with minimal code modification.
|
9
|
+
#
|
10
|
+
# By default, these methods are available only on the `friendly` scope:
|
11
|
+
#
|
12
|
+
# Restaurant.friendly.find('plaza-diner') #=> works
|
13
|
+
# Restaurant.friendly.find(23) #=> also works
|
14
|
+
# Restaurant.find(23) #=> still works
|
15
|
+
# Restaurant.find('plaza-diner') #=> will not work
|
16
|
+
#
|
17
|
+
# ### Restoring FriendlyId 4.0-style finders
|
18
|
+
#
|
19
|
+
# Prior to version 5.0, FriendlyId overrode the default finder methods to perform
|
20
|
+
# friendly finds all the time. This required modifying parts of Rails that did
|
21
|
+
# not have a public API, which was harder to maintain and at times caused
|
22
|
+
# compatiblity problems. In 5.0 we decided to change the library's defaults and add
|
23
|
+
# the friendly finder methods only to the `friendly` scope in order to boost
|
24
|
+
# compatiblity. However, you can still opt-in to original functionality very
|
25
|
+
# easily by using the `:finders` addon:
|
26
|
+
#
|
27
|
+
# class Restaurant < ActiveRecord::Base
|
28
|
+
# extend FriendlyId
|
29
|
+
#
|
30
|
+
# scope :active, -> {where(:active => true)}
|
31
|
+
#
|
32
|
+
# friendly_id :name, :use => [:slugged, :finders]
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Restaurant.friendly.find('plaza-diner') #=> works
|
36
|
+
# Restaurant.find('plaza-diner') #=> now also works
|
37
|
+
# Restaurant.active.find('plaza-diner') #=> now also works
|
38
|
+
#
|
39
|
+
# ### Updating your application to use FriendlyId's finders
|
40
|
+
#
|
41
|
+
# Unless you've chosen to use the `:finders` addon, be sure to modify the finders
|
42
|
+
# in your controllers to use the `friendly` scope. For example:
|
43
|
+
#
|
44
|
+
# # before
|
45
|
+
# def set_restaurant
|
46
|
+
# @restaurant = Restaurant.find(params[:id])
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # after
|
50
|
+
# def set_restaurant
|
51
|
+
# @restaurant = Restaurant.friendly.find(params[:id])
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# #### Active Admin
|
55
|
+
#
|
56
|
+
# Unless you use the `:finders` addon, you should modify your admin controllers
|
57
|
+
# for models that use FriendlyId with something similar to the following:
|
58
|
+
#
|
59
|
+
# controller do
|
60
|
+
# def find_resource
|
61
|
+
# scoped_collection.friendly.find(params[:id])
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# @guide end
|
65
66
|
module Finders
|
66
|
-
|
67
67
|
module ClassMethods
|
68
68
|
if (ActiveRecord::VERSION::MAJOR == 4) && (ActiveRecord::VERSION::MINOR == 0)
|
69
69
|
def relation_delegate_class(klass)
|
70
|
-
relation_class_name = :"#{klass.to_s.gsub(
|
70
|
+
relation_class_name = :"#{klass.to_s.gsub("::", "_")}_#{to_s.gsub("::", "_")}"
|
71
71
|
klass.const_get(relation_class_name)
|
72
72
|
end
|
73
73
|
end
|
@@ -82,7 +82,7 @@ for models that use FriendlyId with something similar to the following:
|
|
82
82
|
end
|
83
83
|
|
84
84
|
# Support for friendly finds on associations for Rails 4.0.1 and above.
|
85
|
-
if ::ActiveRecord.const_defined?(
|
85
|
+
if ::ActiveRecord.const_defined?("AssociationRelation")
|
86
86
|
model_class.extend(ClassMethods)
|
87
87
|
association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation)
|
88
88
|
association_relation_delegate_class.send(:include, model_class.friendly_id_config.finder_methods)
|
data/lib/friendly_id/history.rb
CHANGED
@@ -1,59 +1,58 @@
|
|
1
1
|
module FriendlyId
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
2
|
+
# @guide begin
|
3
|
+
#
|
4
|
+
# ## History: Avoiding 404's When Slugs Change
|
5
|
+
#
|
6
|
+
# FriendlyId's {FriendlyId::History History} module adds the ability to store a
|
7
|
+
# log of a model's slugs, so that when its friendly id changes, it's still
|
8
|
+
# possible to perform finds by the old id.
|
9
|
+
#
|
10
|
+
# The primary use case for this is avoiding broken URLs.
|
11
|
+
#
|
12
|
+
# ### Setup
|
13
|
+
#
|
14
|
+
# In order to use this module, you must add a table to your database schema to
|
15
|
+
# store the slug records. FriendlyId provides a generator for this purpose:
|
16
|
+
#
|
17
|
+
# rails generate friendly_id
|
18
|
+
# rake db:migrate
|
19
|
+
#
|
20
|
+
# This will add a table named `friendly_id_slugs`, used by the {FriendlyId::Slug}
|
21
|
+
# model.
|
22
|
+
#
|
23
|
+
# ### Considerations
|
24
|
+
#
|
25
|
+
# Because recording slug history requires creating additional database records,
|
26
|
+
# this module has an impact on the performance of the associated model's `create`
|
27
|
+
# method.
|
28
|
+
#
|
29
|
+
# ### Example
|
30
|
+
#
|
31
|
+
# class Post < ActiveRecord::Base
|
32
|
+
# extend FriendlyId
|
33
|
+
# friendly_id :title, :use => :history
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# class PostsController < ApplicationController
|
37
|
+
#
|
38
|
+
# before_filter :find_post
|
39
|
+
#
|
40
|
+
# ...
|
41
|
+
#
|
42
|
+
# def find_post
|
43
|
+
# @post = Post.friendly.find params[:id]
|
44
|
+
#
|
45
|
+
# # If an old id or a numeric id was used to find the record, then
|
46
|
+
# # the request slug will not match the current slug, and we should do
|
47
|
+
# # a 301 redirect to the new path
|
48
|
+
# if params[:id] != @post.slug
|
49
|
+
# return redirect_to @post, :status => :moved_permanently
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @guide end
|
55
55
|
module History
|
56
|
-
|
57
56
|
module Configuration
|
58
57
|
def dependent_value
|
59
58
|
dependent.nil? ? :destroy : dependent
|
@@ -72,10 +71,10 @@ method.
|
|
72
71
|
# Configures the model instance to use the History add-on.
|
73
72
|
def self.included(model_class)
|
74
73
|
model_class.class_eval do
|
75
|
-
has_many :slugs, -> {order(id: :desc)}, **{
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
74
|
+
has_many :slugs, -> { order(id: :desc) }, **{
|
75
|
+
as: :sluggable,
|
76
|
+
dependent: @friendly_id_config.dependent_value,
|
77
|
+
class_name: Slug.to_s
|
79
78
|
}
|
80
79
|
|
81
80
|
after_save :create_slug
|
@@ -96,7 +95,7 @@ method.
|
|
96
95
|
end
|
97
96
|
|
98
97
|
def slug_table_record(id)
|
99
|
-
select(quoted_table_name +
|
98
|
+
select(quoted_table_name + ".*").joins(:slugs).where(slug_history_clause(id)).order(Slug.arel_table[:id].desc).first
|
100
99
|
end
|
101
100
|
|
102
101
|
def slug_history_clause(id)
|
@@ -112,7 +111,7 @@ method.
|
|
112
111
|
def scope_for_slug_generator
|
113
112
|
relation = super.joins(:slugs)
|
114
113
|
unless new_record?
|
115
|
-
relation = relation.merge(Slug.where(
|
114
|
+
relation = relation.merge(Slug.where("sluggable_id <> ?", id))
|
116
115
|
end
|
117
116
|
if friendly_id_config.uses?(:scoped)
|
118
117
|
relation = relation.where(Slug.arel_table[:scope].eq(serialized_scope))
|
@@ -124,9 +123,9 @@ method.
|
|
124
123
|
return unless friendly_id
|
125
124
|
return if history_is_up_to_date?
|
126
125
|
# Allow reversion back to a previously used slug
|
127
|
-
relation = slugs.where(:
|
126
|
+
relation = slugs.where(slug: friendly_id)
|
128
127
|
if friendly_id_config.uses?(:scoped)
|
129
|
-
relation = relation.where(:
|
128
|
+
relation = relation.where(scope: serialized_scope)
|
130
129
|
end
|
131
130
|
relation.destroy_all unless relation.empty?
|
132
131
|
slugs.create! do |record|
|
@@ -139,7 +138,7 @@ method.
|
|
139
138
|
latest_history = slugs.first
|
140
139
|
check = latest_history.try(:slug) == friendly_id
|
141
140
|
if friendly_id_config.uses?(:scoped)
|
142
|
-
check
|
141
|
+
check &&= latest_history.scope == serialized_scope
|
143
142
|
end
|
144
143
|
check
|
145
144
|
end
|
@@ -16,9 +16,9 @@ FriendlyId.defaults do |config|
|
|
16
16
|
# undesirable to allow as slugs. Edit this list as needed for your app.
|
17
17
|
config.use :reserved
|
18
18
|
|
19
|
-
config.reserved_words = %w
|
20
|
-
stylesheets assets javascripts images
|
21
|
-
|
19
|
+
config.reserved_words = %w[new edit index session login logout users admin
|
20
|
+
stylesheets assets javascripts images]
|
21
|
+
|
22
22
|
# This adds an option to treat reserved words as conflicts rather than exceptions.
|
23
23
|
# When there is no good candidate, a UUID will be appended, matching the existing
|
24
24
|
# conflict behavior.
|
@@ -37,7 +37,7 @@ FriendlyId.defaults do |config|
|
|
37
37
|
# MyModel.find('foo')
|
38
38
|
#
|
39
39
|
# This is significantly more convenient but may not be appropriate for
|
40
|
-
# all applications, so you must
|
40
|
+
# all applications, so you must explicitly opt-in to this behavior. You can
|
41
41
|
# always also configure it on a per-model basis if you prefer.
|
42
42
|
#
|
43
43
|
# Something else to consider is that using the :finders addon boosts
|
@@ -8,14 +8,14 @@ MIGRATION_CLASS =
|
|
8
8
|
class CreateFriendlyIdSlugs < MIGRATION_CLASS
|
9
9
|
def change
|
10
10
|
create_table :friendly_id_slugs do |t|
|
11
|
-
t.string
|
12
|
-
t.integer
|
13
|
-
t.string
|
14
|
-
t.string
|
11
|
+
t.string :slug, null: false
|
12
|
+
t.integer :sluggable_id, null: false
|
13
|
+
t.string :sluggable_type, limit: 50
|
14
|
+
t.string :scope
|
15
15
|
t.datetime :created_at
|
16
16
|
end
|
17
17
|
add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id]
|
18
|
-
add_index :friendly_id_slugs, [:slug, :sluggable_type], length: {
|
19
|
-
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: {
|
18
|
+
add_index :friendly_id_slugs, [:slug, :sluggable_type], length: {slug: 140, sluggable_type: 50}
|
19
|
+
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: {slug: 70, sluggable_type: 50, scope: 70}, unique: true
|
20
20
|
end
|
21
21
|
end
|
@@ -19,7 +19,6 @@ module FriendlyId
|
|
19
19
|
# names that unambigously refer to the library of their origin, which should
|
20
20
|
# be sufficient to avoid conflicts with other libraries.
|
21
21
|
module ObjectUtils
|
22
|
-
|
23
22
|
# True if the id is definitely friendly, false if definitely unfriendly,
|
24
23
|
# else nil.
|
25
24
|
#
|
@@ -45,7 +44,8 @@ module FriendlyId
|
|
45
44
|
# True if the id is definitely unfriendly, false if definitely friendly,
|
46
45
|
# else nil.
|
47
46
|
def unfriendly_id?
|
48
|
-
val = friendly_id?
|
47
|
+
val = friendly_id?
|
48
|
+
!val unless val.nil?
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
data/lib/friendly_id/reserved.rb
CHANGED
@@ -1,42 +1,40 @@
|
|
1
1
|
module FriendlyId
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
=end
|
2
|
+
# @guide begin
|
3
|
+
#
|
4
|
+
# ## Reserved Words
|
5
|
+
#
|
6
|
+
# The {FriendlyId::Reserved Reserved} module adds the ability to exclude a list of
|
7
|
+
# words from use as FriendlyId slugs.
|
8
|
+
#
|
9
|
+
# With Ruby on Rails, FriendlyId's generator generates an initializer that
|
10
|
+
# reserves some words such as "new" and "edit" using {FriendlyId.defaults
|
11
|
+
# FriendlyId.defaults}.
|
12
|
+
#
|
13
|
+
# Note that the error messages for fields will appear on the field
|
14
|
+
# `:friendly_id`. If you are using Rails's scaffolded form errors display, then
|
15
|
+
# it will have no field to highlight. If you'd like to change this so that
|
16
|
+
# scaffolding works as expected, one way to accomplish this is to move the error
|
17
|
+
# message to a different field. For example:
|
18
|
+
#
|
19
|
+
# class Person < ActiveRecord::Base
|
20
|
+
# extend FriendlyId
|
21
|
+
# friendly_id :name, use: :slugged
|
22
|
+
#
|
23
|
+
# after_validation :move_friendly_id_error_to_name
|
24
|
+
#
|
25
|
+
# def move_friendly_id_error_to_name
|
26
|
+
# errors.add :name, *errors.delete(:friendly_id) if errors[:friendly_id].present?
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @guide end
|
32
31
|
module Reserved
|
33
|
-
|
34
32
|
# When included, this module adds configuration options to the model class's
|
35
33
|
# friendly_id_config.
|
36
34
|
def self.included(model_class)
|
37
35
|
model_class.class_eval do
|
38
36
|
friendly_id_config.class.send :include, Reserved::Configuration
|
39
|
-
validates_exclusion_of :friendly_id, :
|
37
|
+
validates_exclusion_of :friendly_id, in: ->(_) {
|
40
38
|
friendly_id_config.reserved_words || []
|
41
39
|
}
|
42
40
|
end
|