friendly_id 5.5.0 → 5.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b1293d744a87c9988c0ff2dfba773f351cdf62bc24e3b3c73e386310b44d4bb
4
- data.tar.gz: 1e02363f0abe107ce5165942024ff4cdabfc8b8c18083e0615846c58f5e41252
3
+ metadata.gz: 41f8cc1c8668551a87b0537b5a30b812ec635755c7b1d0bfda678bc4be23b84c
4
+ data.tar.gz: 7cc6753a1f56537561558134ceae5c796b2e1296fa5230b0b734ddebd17be789
5
5
  SHA512:
6
- metadata.gz: f7f27736b4ae2218c95c42cde457472a919c81f89d0b899453c351e4b16c2e557c6f44a9dc32ef947123e2228e5588f8bcd1157c60d3ecb4f4195cc4687609b9
7
- data.tar.gz: bf6b4a73627faa1363a78d70d6a82e3e1ebf2fa1b7979504d0914646325fffc980f9b78508a3a9ad8ab17c93034bc45c2b50cf3d058f33857b4e83a064c605fc
6
+ metadata.gz: cbfde34d6b2cd2347ab9837101824e65504fa226d51e4820e87b5cc7f2546c3e55decb2c7d0df4f8d1d899ef517f8177a5f63543a5577194071b7388f10cb5cf
7
+ data.tar.gz: f13c197800451a77c86092fa15fcd730ae669dca4486856ddf1dce21be7ad6ee8bbed41590f0a3afb1d72d804c343e4b5050f6c906090a7c951fb54b46c1d936
@@ -0,0 +1,29 @@
1
+ name: Release to rubygems.org
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+
8
+ jobs:
9
+ push:
10
+ runs-on: ubuntu-latest
11
+
12
+ permissions:
13
+ contents: write
14
+ id-token: write
15
+
16
+ environment: release
17
+
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ with:
21
+ persist-credentials: false
22
+
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ bundler-cache: true
27
+ ruby-version: ruby
28
+
29
+ - uses: rubygems/release-gem@v1
@@ -10,11 +10,39 @@ jobs:
10
10
  strategy:
11
11
  matrix:
12
12
  database: [ mysql, postgresql ]
13
- gemfile: [ '7.0', '6.1', '6.0' ]
14
- ruby: [ '2.6', '2.7', '3.0', '3.1' ]
13
+ gemfile: [ '8.0', '7.2', '7.1', '7.0', '6.1', '6.0' ]
14
+ ruby: [ '2.7', '3.0', '3.1', '3.2', '3.3', '3.4' ]
15
15
  exclude:
16
- - ruby: '2.6'
17
- gemfile: '7.0'
16
+ - gemfile: '6.0'
17
+ ruby: '3.2'
18
+ - gemfile: '6.0'
19
+ ruby: '3.3'
20
+ - gemfile: '6.0'
21
+ ruby: '3.4'
22
+ - gemfile: '6.1'
23
+ ruby: '3.2'
24
+ - gemfile: '6.1'
25
+ ruby: '3.3'
26
+ - gemfile: '6.1'
27
+ ruby: '3.4'
28
+ - gemfile: '7.0'
29
+ ruby: '3.3'
30
+ - gemfile: '7.0'
31
+ ruby: '3.4'
32
+ - gemfile: '7.1'
33
+ ruby: '3.3'
34
+ - gemfile: '7.1'
35
+ ruby: '3.4'
36
+ - gemfile: '7.2'
37
+ ruby: '2.7'
38
+ - gemfile: '7.2'
39
+ ruby: '3.0'
40
+ - gemfile: '8.0'
41
+ ruby: '2.7'
42
+ - gemfile: '8.0'
43
+ ruby: '3.0'
44
+ - gemfile: '8.0'
45
+ ruby: '3.1'
18
46
  fail-fast: false
19
47
  runs-on: ubuntu-latest
20
48
 
@@ -31,7 +59,7 @@ jobs:
31
59
 
32
60
  name: ${{ matrix.ruby }} ${{ matrix.database }} rails-${{ matrix.gemfile }}
33
61
  steps:
34
- - uses: actions/checkout@v3
62
+ - uses: actions/checkout@v6
35
63
 
36
64
  - run: sudo apt-get update && sudo apt-get install libsqlite3-dev -y
37
65
 
data/.yardopts CHANGED
@@ -1,5 +1,7 @@
1
1
  -e guide.rb
2
2
  --files=Changelog.md,Guide.md
3
+ --tag guide
4
+ --hide-tag guide
3
5
  --private
4
6
  --protected
5
7
  --exclude lib/friendly_id/migration
data/Changelog.md CHANGED
@@ -3,7 +3,14 @@
3
3
  We would like to think our many [contributors](https://github.com/norman/friendly_id/graphs/contributors) for
4
4
  suggestions, ideas and improvements to FriendlyId.
5
5
 
6
- ## Unreleased
6
+ ## 5.6.0 (2025-12-07)
7
+
8
+ * Add: `treat_numeric_as_conflict` option to prevent ambiguous numeric slugs. ([#1037](https://github.com/norman/friendly_id/pull/1037))
9
+ * Fix: history not using `parse_friendly_id`. ([#1020](https://github.com/norman/friendly_id/pull/1020))
10
+
11
+ ## 5.5.1 (2023-11-13)
12
+
13
+ * Fix YARD doc generation. ([#1006](https://github.com/norman/friendly_id/pull/1006))
7
14
 
8
15
  ## 5.5.0 (2022-11-16)
9
16
 
data/README.md CHANGED
@@ -39,7 +39,7 @@ FriendlyId offers many advanced features, including:
39
39
  Add this line to your application's Gemfile:
40
40
 
41
41
  ```ruby
42
- gem 'friendly_id', '~> 5.4.0'
42
+ gem 'friendly_id', '~> 5.5.0'
43
43
  ```
44
44
 
45
45
  Note: You MUST use 5.0.0 or greater for Rails 4.0+.
@@ -108,8 +108,8 @@ User.find_each(&:save)
108
108
 
109
109
  ### `:allow_nil`
110
110
 
111
- You can pass `allow_nil: true` to the `friendly.find()` method if you're want to
112
- avoid raising `ActiveRecord::RecordNotFound` and accept a `nil`.
111
+ You can pass `allow_nil: true` to the `friendly.find()` method if you want to
112
+ avoid raising `ActiveRecord::RecordNotFound` and accept `nil`.
113
113
 
114
114
  #### Example
115
115
 
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+ require "bundler/gem_tasks"
2
3
  require "rake/testtask"
3
4
 
4
5
  task default: :test
@@ -21,11 +22,6 @@ task :clean do
21
22
  %x(rm -f `find . -name '*.rbc'`)
22
23
  end
23
24
 
24
- desc "Build the gem"
25
- task :gem do
26
- `gem build friendly_id.gemspec`
27
- end
28
-
29
25
  desc "Build YARD documentation"
30
26
  task :yard do
31
27
  puts `bundle exec yard`
data/friendly_id.gemspec CHANGED
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
4
4
  s.name = "friendly_id"
5
5
  s.version = FriendlyId::VERSION
6
6
  s.authors = ["Norman Clarke", "Philip Arndt"]
7
- s.email = ["norman@njclarke.com", "p@arndt.io"]
7
+ s.email = ["norman@njclarke.com", "gems@p.arndt.io"]
8
8
  s.homepage = "https://github.com/norman/friendly_id"
9
9
  s.summary = "A comprehensive slugging and pretty-URL plugin."
10
10
  s.files = `git ls-files`.split("\n")
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_development_dependency "coveralls"
20
20
  s.add_development_dependency "railties", ">= 4.0"
21
21
  s.add_development_dependency "minitest", "~> 5.3"
22
- s.add_development_dependency "mocha", "~> 1.1"
22
+ s.add_development_dependency "mocha", "~> 2.1"
23
23
  s.add_development_dependency "yard"
24
24
  s.add_development_dependency "i18n"
25
25
  s.add_development_dependency "ffaker"
@@ -28,9 +28,4 @@ Gem::Specification.new do |s|
28
28
  s.description = "FriendlyId is the \"Swiss Army bulldozer\" of slugging " \
29
29
  "and permalink plugins for Active Record. It lets you create pretty URLs " \
30
30
  "and work with human-friendly strings as if they were numeric ids."
31
-
32
- s.cert_chain = [File.expand_path("certs/parndt.pem", __dir__)]
33
- if $PROGRAM_NAME.end_with?("gem") && ARGV.include?("build") && ARGV.include?(__FILE__)
34
- s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem")
35
- end
36
31
  end
@@ -2,14 +2,14 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec path: "../"
4
4
 
5
- gem "activerecord", "~> 5.2.0"
6
- gem "railties", "~> 5.2.0"
5
+ gem "activerecord", "~> 7.1.0"
6
+ gem "railties", "~> 7.1.0"
7
7
 
8
8
  # Database Configuration
9
9
  group :development, :test do
10
10
  platforms :jruby do
11
- gem "activerecord-jdbcmysql-adapter", "~> 51.1"
12
- gem "activerecord-jdbcpostgresql-adapter", "~> 51.1"
11
+ gem "activerecord-jdbcmysql-adapter", "~> 61.0"
12
+ gem "activerecord-jdbcpostgresql-adapter", "~> 61.0"
13
13
  gem "kramdown"
14
14
  end
15
15
 
@@ -0,0 +1,22 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "activerecord", "~> 7.2.0"
6
+ gem "railties", "~> 7.2.0"
7
+
8
+ # Database Configuration
9
+ group :development, :test do
10
+ platforms :jruby do
11
+ gem "activerecord-jdbcmysql-adapter", "~> 61.0"
12
+ gem "activerecord-jdbcpostgresql-adapter", "~> 61.0"
13
+ gem "kramdown"
14
+ end
15
+
16
+ platforms :ruby, :rbx do
17
+ gem "sqlite3"
18
+ gem "mysql2"
19
+ gem "pg"
20
+ gem "redcarpet"
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "activerecord", "~> 8.0.0"
6
+ gem "railties", "~> 8.0.0"
7
+
8
+ # Database Configuration
9
+ group :development, :test do
10
+ platforms :jruby do
11
+ gem "activerecord-jdbcmysql-adapter", "~> 61.0"
12
+ gem "activerecord-jdbcpostgresql-adapter", "~> 61.0"
13
+ gem "kramdown"
14
+ end
15
+
16
+ platforms :ruby, :rbx do
17
+ gem "sqlite3"
18
+ gem "mysql2"
19
+ gem "pg"
20
+ gem "redcarpet"
21
+ end
22
+ end
data/guide.rb CHANGED
@@ -4,8 +4,15 @@
4
4
 
5
5
  def comments_from path
6
6
  path = File.expand_path("../lib/friendly_id/#{path}", __FILE__)
7
- match = File.read(path).match(/\n=begin(.*)\n=end/m)[1].to_s
8
- match.split("\n").reject { |x| x =~ /^@/ }.join("\n").strip
7
+ matches = File.read(path).match(/\n\s*# @guide begin\n(.*)\s*# @guide end/m)
8
+
9
+ return if matches.nil?
10
+
11
+ match = matches[1].to_s
12
+ match.split("\n")
13
+ .map { |x| x.sub(/^\s*#\s?/, "") } # Strip off the comment, leading whitespace, and the space after the comment
14
+ .reject { |x| x =~ /^@/ } # Ignore yarddoc tags for the guide
15
+ .join("\n").strip
9
16
  end
10
17
 
11
18
  File.open(File.expand_path("../Guide.md", __FILE__), "w:utf-8") do |guide|
@@ -1,6 +1,7 @@
1
1
  module FriendlyId
2
+ # @guide begin
2
3
  #
3
- ## Setting Up FriendlyId in Your Model
4
+ # ## Setting Up FriendlyId in Your Model
4
5
  #
5
6
  # To use FriendlyId in your ActiveRecord models, you must first either extend or
6
7
  # include the FriendlyId module (it makes no difference), then invoke the
@@ -21,7 +22,7 @@ module FriendlyId
21
22
  # all classes that participate in STI, both your parent classes and their
22
23
  # children.*
23
24
  #
24
- ### The Default Setup: Simple Models
25
+ # ### The Default Setup: Simple Models
25
26
  #
26
27
  # The simplest way to use FriendlyId is with a model that has a uniquely indexed
27
28
  # column with no spaces or special characters, and that is seldom or never
@@ -53,6 +54,7 @@ module FriendlyId
53
54
  # in a URL can be repetitive and surprisingly tricky, so for this reason it's
54
55
  # often better and easier to use {FriendlyId::Slugged slugs}.
55
56
  #
57
+ # @guide end
56
58
  module Base
57
59
  # Configure FriendlyId's behavior in a model.
58
60
  #
@@ -1,4 +1,6 @@
1
1
  module FriendlyId
2
+ # @guide begin
3
+ #
2
4
  # ## Performing Finds with FriendlyId
3
5
  #
4
6
  # FriendlyId offers enhanced finders which will search for your record by
@@ -12,7 +14,7 @@ module FriendlyId
12
14
  # Restaurant.find(23) #=> still works
13
15
  # Restaurant.find('plaza-diner') #=> will not work
14
16
  #
15
- ### Restoring FriendlyId 4.0-style finders
17
+ # ### Restoring FriendlyId 4.0-style finders
16
18
  #
17
19
  # Prior to version 5.0, FriendlyId overrode the default finder methods to perform
18
20
  # friendly finds all the time. This required modifying parts of Rails that did
@@ -34,7 +36,7 @@ module FriendlyId
34
36
  # Restaurant.find('plaza-diner') #=> now also works
35
37
  # Restaurant.active.find('plaza-diner') #=> now also works
36
38
  #
37
- ### Updating your application to use FriendlyId's finders
39
+ # ### Updating your application to use FriendlyId's finders
38
40
  #
39
41
  # Unless you've chosen to use the `:finders` addon, be sure to modify the finders
40
42
  # in your controllers to use the `friendly` scope. For example:
@@ -49,7 +51,7 @@ module FriendlyId
49
51
  # @restaurant = Restaurant.friendly.find(params[:id])
50
52
  # end
51
53
  #
52
- #### Active Admin
54
+ # #### Active Admin
53
55
  #
54
56
  # Unless you use the `:finders` addon, you should modify your admin controllers
55
57
  # for models that use FriendlyId with something similar to the following:
@@ -60,6 +62,7 @@ module FriendlyId
60
62
  # end
61
63
  # end
62
64
  #
65
+ # @guide end
63
66
  module Finders
64
67
  module ClassMethods
65
68
  if (ActiveRecord::VERSION::MAJOR == 4) && (ActiveRecord::VERSION::MINOR == 0)
@@ -1,6 +1,7 @@
1
1
  module FriendlyId
2
+ # @guide begin
2
3
  #
3
- ## History: Avoiding 404's When Slugs Change
4
+ # ## History: Avoiding 404's When Slugs Change
4
5
  #
5
6
  # FriendlyId's {FriendlyId::History History} module adds the ability to store a
6
7
  # log of a model's slugs, so that when its friendly id changes, it's still
@@ -8,7 +9,7 @@ module FriendlyId
8
9
  #
9
10
  # The primary use case for this is avoiding broken URLs.
10
11
  #
11
- ### Setup
12
+ # ### Setup
12
13
  #
13
14
  # In order to use this module, you must add a table to your database schema to
14
15
  # store the slug records. FriendlyId provides a generator for this purpose:
@@ -19,13 +20,13 @@ module FriendlyId
19
20
  # This will add a table named `friendly_id_slugs`, used by the {FriendlyId::Slug}
20
21
  # model.
21
22
  #
22
- ### Considerations
23
+ # ### Considerations
23
24
  #
24
25
  # Because recording slug history requires creating additional database records,
25
26
  # this module has an impact on the performance of the associated model's `create`
26
27
  # method.
27
28
  #
28
- ### Example
29
+ # ### Example
29
30
  #
30
31
  # class Post < ActiveRecord::Base
31
32
  # extend FriendlyId
@@ -49,6 +50,8 @@ module FriendlyId
49
50
  # end
50
51
  # end
51
52
  # end
53
+ #
54
+ # @guide end
52
55
  module History
53
56
  module Configuration
54
57
  def dependent_value
@@ -82,13 +85,13 @@ module FriendlyId
82
85
  include ::FriendlyId::FinderMethods
83
86
 
84
87
  def exists_by_friendly_id?(id)
85
- super || joins(:slugs).where(slug_history_clause(id)).exists?
88
+ super || joins(:slugs).where(slug_history_clause(parse_friendly_id(id))).exists?
86
89
  end
87
90
 
88
91
  private
89
92
 
90
93
  def first_by_friendly_id(id)
91
- super || slug_table_record(id)
94
+ super || slug_table_record(parse_friendly_id(id))
92
95
  end
93
96
 
94
97
  def slug_table_record(id)
@@ -1,6 +1,7 @@
1
1
  module FriendlyId
2
+ # @guide begin
2
3
  #
3
- ## Reserved Words
4
+ # ## Reserved Words
4
5
  #
5
6
  # The {FriendlyId::Reserved Reserved} module adds the ability to exclude a list of
6
7
  # words from use as FriendlyId slugs.
@@ -26,6 +27,7 @@ module FriendlyId
26
27
  # end
27
28
  # end
28
29
  #
30
+ # @guide end
29
31
  module Reserved
30
32
  # When included, this module adds configuration options to the model class's
31
33
  # friendly_id_config.
@@ -1,8 +1,9 @@
1
1
  require "friendly_id/slugged"
2
2
 
3
3
  module FriendlyId
4
+ # @guide begin
4
5
  #
5
- ## Unique Slugs by Scope
6
+ # ## Unique Slugs by Scope
6
7
  #
7
8
  # The {FriendlyId::Scoped} module allows FriendlyId to generate unique slugs
8
9
  # within a scope.
@@ -53,7 +54,7 @@ module FriendlyId
53
54
  #
54
55
  # All supplied values will be used to determine scope.
55
56
  #
56
- ### Finding Records by Friendly ID
57
+ # ### Finding Records by Friendly ID
57
58
  #
58
59
  # If you are using scopes your friendly ids may not be unique, so a simple find
59
60
  # like:
@@ -70,13 +71,13 @@ module FriendlyId
70
71
  # Restaurant.where(:city_id => @city.id).friendly.find("joes-diner")
71
72
  #
72
73
  #
73
- ### Finding All Records That Match a Scoped ID
74
+ # ### Finding All Records That Match a Scoped ID
74
75
  #
75
76
  # Query the slug column directly:
76
77
  #
77
78
  # Restaurant.where(:slug => "joes-diner")
78
79
  #
79
- ### Routes for Scoped Models
80
+ # ### Routes for Scoped Models
80
81
  #
81
82
  # Recall that FriendlyId is a database-centric library, and does not set up any
82
83
  # routes for scoped models. You must do this yourself in your application. Here's
@@ -98,6 +99,7 @@ module FriendlyId
98
99
  # http://example.org/cities/seattle/restaurants/joes-diner
99
100
  # http://example.org/cities/chicago/restaurants/joes-diner
100
101
  #
102
+ # @guide end
101
103
  module Scoped
102
104
  # FriendlyId::Config.use will invoke this method when present, to allow
103
105
  # loading dependent modules prior to overriding them when necessary.
@@ -1,8 +1,9 @@
1
1
  require "i18n"
2
2
 
3
3
  module FriendlyId
4
+ # @guide begin
4
5
  #
5
- ## Translating Slugs Using Simple I18n
6
+ # ## Translating Slugs Using Simple I18n
6
7
  #
7
8
  # The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to
8
9
  # FriendlyId.
@@ -17,7 +18,7 @@ module FriendlyId
17
18
  # If you need to support two or more locales, you may wish to use the
18
19
  # friendly_id_globalize gem instead.
19
20
  #
20
- ### Example migration
21
+ # ### Example migration
21
22
  #
22
23
  # def self.up
23
24
  # create_table :posts do |t|
@@ -32,7 +33,7 @@ module FriendlyId
32
33
  # add_index :posts, :slug_pt_br
33
34
  # end
34
35
  #
35
- ### Finds
36
+ # ### Finds
36
37
  #
37
38
  # Finds will take into consideration the current locale:
38
39
  #
@@ -50,11 +51,11 @@ module FriendlyId
50
51
  # Post.friendly.find("la-guerra-de-las-galaxias")
51
52
  # end
52
53
  #
53
- ### Creating Records
54
+ # ### Creating Records
54
55
  #
55
56
  # When new records are created, the slug is generated for the current locale only.
56
57
  #
57
- ### Translating Slugs
58
+ # ### Translating Slugs
58
59
  #
59
60
  # To translate an existing record's friendly_id, use
60
61
  # {FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug
@@ -69,6 +70,8 @@ module FriendlyId
69
70
  # I18n.with_locale(:es) do
70
71
  # post.set_friendly_id("La guerra de las galaxias")
71
72
  # end
73
+ #
74
+ # @guide end
72
75
  module SimpleI18n
73
76
  # FriendlyId::Config.use will invoke this method when present, to allow
74
77
  # loading dependent modules prior to overriding them when necessary.
@@ -12,6 +12,10 @@ module FriendlyId
12
12
  return false if @config.reserved_words.include?(slug)
13
13
  end
14
14
 
15
+ if @config.treat_numeric_as_conflict && purely_numeric_slug?(slug)
16
+ return false
17
+ end
18
+
15
19
  !@scope.exists_by_friendly_id?(slug)
16
20
  end
17
21
 
@@ -19,5 +23,16 @@ module FriendlyId
19
23
  candidates.each { |c| return c if available?(c) }
20
24
  nil
21
25
  end
26
+
27
+ private
28
+
29
+ def purely_numeric_slug?(slug)
30
+ return false unless slug
31
+ begin
32
+ Integer(slug, 10).to_s == slug.to_s
33
+ rescue ArgumentError, TypeError
34
+ false
35
+ end
36
+ end
22
37
  end
23
38
  end
@@ -2,8 +2,9 @@ require "friendly_id/slug_generator"
2
2
  require "friendly_id/candidates"
3
3
 
4
4
  module FriendlyId
5
+ # @guide begin
5
6
  #
6
- ## Slugged Models
7
+ # ## Slugged Models
7
8
  #
8
9
  # FriendlyId can use a separate column to store slugs for models which require
9
10
  # some text processing.
@@ -34,7 +35,7 @@ module FriendlyId
34
35
  # unique. You may also wish to constrain it to NOT NULL, but this depends on your
35
36
  # app's behavior and requirements.
36
37
  #
37
- ### Example Setup
38
+ # ### Example Setup
38
39
  #
39
40
  # # your model
40
41
  # class Post < ActiveRecord::Base
@@ -60,9 +61,9 @@ module FriendlyId
60
61
  # end
61
62
  # end
62
63
  #
63
- ### Working With Slugs
64
+ # ### Working With Slugs
64
65
  #
65
- #### Formatting
66
+ # #### Formatting
66
67
  #
67
68
  # By default, FriendlyId uses Active Support's
68
69
  # [parameterize](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
@@ -72,7 +73,7 @@ module FriendlyId
72
73
  # movie = Movie.create! :title => "Der Preis fürs Überleben"
73
74
  # movie.slug #=> "der-preis-furs-uberleben"
74
75
  #
75
- #### Column or Method?
76
+ # #### Column or Method?
76
77
  #
77
78
  # FriendlyId always uses a method as the basis of the slug text - not a column. At
78
79
  # first glance, this may sound confusing, but remember that Active Record provides
@@ -95,7 +96,7 @@ module FriendlyId
95
96
  #
96
97
  # FriendlyId refers to this internally as the "base" method.
97
98
  #
98
- #### Uniqueness
99
+ # #### Uniqueness
99
100
  #
100
101
  # When you try to insert a record that would generate a duplicate friendly id,
101
102
  # FriendlyId will append a UUID to the generated slug to ensure uniqueness:
@@ -109,7 +110,7 @@ module FriendlyId
109
110
  # Previous versions of FriendlyId appended a numeric sequence to make slugs
110
111
  # unique, but this was removed to simplify using FriendlyId in concurrent code.
111
112
  #
112
- #### Candidates
113
+ # #### Candidates
113
114
  #
114
115
  # Since UUIDs are ugly, FriendlyId provides a "slug candidates" functionality to
115
116
  # let you specify alternate slugs to use in the event the one you want to use is
@@ -147,20 +148,38 @@ module FriendlyId
147
148
  # unique slug, then FriendlyId will append a UUID to the first candidate as a
148
149
  # last resort.
149
150
  #
150
- #### Sequence Separator
151
+ # #### Sequence Separator
151
152
  #
152
153
  # By default, FriendlyId uses a dash to separate the slug from a sequence.
153
154
  #
154
155
  # You can change this with the {FriendlyId::Slugged::Configuration#sequence_separator
155
156
  # sequence_separator} configuration option.
156
157
  #
157
- #### Providing Your Own Slug Processing Method
158
+ # #### Avoiding Numeric Slugs
159
+ #
160
+ # Purely numeric slugs like "123" can create ambiguity in Rails routing - they could
161
+ # be interpreted as either a friendly slug or a database ID. To prevent this, you can
162
+ # configure FriendlyId to treat numeric slugs as conflicts and add a UUID suffix:
163
+ #
164
+ # class Product < ActiveRecord::Base
165
+ # extend FriendlyId
166
+ # friendly_id :sku, use: :slugged
167
+ # friendly_id_config.treat_numeric_as_conflict = true
168
+ # end
169
+ #
170
+ # product = Product.create! sku: "123"
171
+ # product.slug #=> "123-f9f3789a-daec-4156-af1d-fab81aa16ee5"
172
+ #
173
+ # This only affects purely numeric slugs. Alphanumeric slugs like "product-123"
174
+ # or "abc123" will work normally.
175
+ #
176
+ # #### Providing Your Own Slug Processing Method
158
177
  #
159
178
  # You can override {FriendlyId::Slugged#normalize_friendly_id} in your model for
160
179
  # total control over the slug format. It will be invoked for any generated slug,
161
180
  # whether for a single slug or for slug candidates.
162
181
  #
163
- #### Deciding When to Generate New Slugs
182
+ # #### Deciding When to Generate New Slugs
164
183
  #
165
184
  # As of FriendlyId 5.0, slugs are only generated when the `slug` field is nil. If
166
185
  # you want a slug to be regenerated,set the slug field to nil:
@@ -198,7 +217,7 @@ module FriendlyId
198
217
  # end
199
218
  # end
200
219
  #
201
- #### Locale-specific Transliterations
220
+ # #### Locale-specific Transliterations
202
221
  #
203
222
  # Active Support's `parameterize` uses
204
223
  # [transliterate](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate),
@@ -219,7 +238,7 @@ module FriendlyId
219
238
  #
220
239
  # This functionality was in fact taken from earlier versions of FriendlyId.
221
240
  #
222
- #### Gotchas: Common Problems
241
+ # #### Gotchas: Common Problems
223
242
  #
224
243
  # FriendlyId uses a before_validation callback to generate and set the slug. This
225
244
  # means that if you create two model instances before saving them, it's possible
@@ -233,6 +252,7 @@ module FriendlyId
233
252
  # creating more than one nested record for a model that uses FriendlyId. See [this
234
253
  # Github issue](https://github.com/norman/friendly_id/issues/185) for discussion.
235
254
  #
255
+ # @guide end
236
256
  module Slugged
237
257
  # Sets up behavior and configuration options for FriendlyId's slugging
238
258
  # feature.
@@ -384,7 +404,7 @@ module FriendlyId
384
404
  # {FriendlyId::Configuration FriendlyId::Configuration}.
385
405
  module Configuration
386
406
  attr_writer :slug_column, :slug_limit, :sequence_separator
387
- attr_accessor :slug_generator_class
407
+ attr_accessor :slug_generator_class, :treat_numeric_as_conflict
388
408
 
389
409
  # Makes FriendlyId use the slug column for querying.
390
410
  # @return String The slug column.
@@ -1,3 +1,3 @@
1
1
  module FriendlyId
2
- VERSION = "5.5.0".freeze
2
+ VERSION = "5.6.0".freeze
3
3
  end
data/lib/friendly_id.rb CHANGED
@@ -4,8 +4,9 @@ require "friendly_id/object_utils"
4
4
  require "friendly_id/configuration"
5
5
  require "friendly_id/finder_methods"
6
6
 
7
+ # @guide begin
7
8
  #
8
- ## About FriendlyId
9
+ # ## About FriendlyId
9
10
  #
10
11
  # FriendlyId is an add-on to Ruby's Active Record that allows you to replace ids
11
12
  # in your URLs with strings:
@@ -19,9 +20,9 @@ require "friendly_id/finder_methods"
19
20
  # It requires few changes to your application code and offers flexibility,
20
21
  # performance and a well-documented codebase.
21
22
  #
22
- ### Core Concepts
23
+ # ### Core Concepts
23
24
  #
24
- #### Slugs
25
+ # #### Slugs
25
26
  #
26
27
  # The concept of *slugs* is at the heart of FriendlyId.
27
28
  #
@@ -29,7 +30,7 @@ require "friendly_id/finder_methods"
29
30
  # keywords, rather than an opaque identifier such as a numeric id. This can make
30
31
  # your application more friendly both for users and search engines.
31
32
  #
32
- #### Finders: Slugs Act Like Numeric IDs
33
+ # #### Finders: Slugs Act Like Numeric IDs
33
34
  #
34
35
  # To the extent possible, FriendlyId lets you treat text-based identifiers like
35
36
  # normal IDs. This means that you can perform finds with slugs just like you do
@@ -38,6 +39,7 @@ require "friendly_id/finder_methods"
38
39
  # Person.find(82542335)
39
40
  # Person.friendly.find("joe")
40
41
  #
42
+ # @guide end
41
43
  module FriendlyId
42
44
  autoload :History, "friendly_id/history"
43
45
  autoload :Slug, "friendly_id/slug"
data/test/helper.rb CHANGED
@@ -19,9 +19,9 @@ rescue LoadError
19
19
  end
20
20
 
21
21
  begin
22
- TestCaseClass = MiniTest::Test
22
+ TestCaseClass = Minitest::Test
23
23
  rescue NameError
24
- TestCaseClass = MiniTest::Unit::TestCase
24
+ TestCaseClass = Minitest::Unit::TestCase
25
25
  end
26
26
 
27
27
  require "mocha/minitest"
@@ -1,5 +1,17 @@
1
1
  require "helper"
2
2
 
3
+ class Article < ActiveRecord::Base
4
+ extend FriendlyId
5
+ friendly_id :name, use: :slugged
6
+ end
7
+
8
+ class ArticleWithNumericPrevention < ActiveRecord::Base
9
+ self.table_name = "articles"
10
+ extend FriendlyId
11
+ friendly_id :name, use: :slugged
12
+ friendly_id_config.treat_numeric_as_conflict = true
13
+ end
14
+
3
15
  class NumericSlugTest < TestCaseClass
4
16
  include FriendlyId::Test
5
17
  include FriendlyId::Test::Shared::Core
@@ -28,4 +40,61 @@ class NumericSlugTest < TestCaseClass
28
40
  assert model_class.friendly.exists?("123")
29
41
  end
30
42
  end
43
+
44
+ test "should prevent purely numeric slugs when treat_numeric_as_conflict is enabled" do
45
+ transaction do
46
+ record = ArticleWithNumericPrevention.create! name: "123"
47
+ refute_equal "123", record.slug
48
+ assert_match(/\A123-[0-9a-f-]{36}\z/, record.slug)
49
+ end
50
+ end
51
+
52
+ test "should allow non-numeric slugs when treat_numeric_as_conflict is enabled" do
53
+ transaction do
54
+ record = ArticleWithNumericPrevention.create! name: "abc123"
55
+ assert_equal "abc123", record.slug
56
+ end
57
+ end
58
+
59
+ test "should allow alphanumeric slugs when treat_numeric_as_conflict is enabled" do
60
+ transaction do
61
+ record = ArticleWithNumericPrevention.create! name: "product-123"
62
+ assert_equal "product-123", record.slug
63
+ end
64
+ end
65
+
66
+ test "should handle zero as numeric when treat_numeric_as_conflict is enabled" do
67
+ transaction do
68
+ record = ArticleWithNumericPrevention.create! name: "0"
69
+ refute_equal "0", record.slug
70
+ assert_match(/\A0-[0-9a-f-]{36}\z/, record.slug)
71
+ end
72
+ end
73
+
74
+ test "should handle large numbers as numeric when treat_numeric_as_conflict is enabled" do
75
+ transaction do
76
+ record = ArticleWithNumericPrevention.create! name: "999999999"
77
+ refute_equal "999999999", record.slug
78
+ assert_match(/\A999999999-[0-9a-f-]{36}\z/, record.slug)
79
+ end
80
+ end
81
+
82
+ test "should find records with UUID-suffixed numeric slugs when treat_numeric_as_conflict is enabled" do
83
+ transaction do
84
+ record = ArticleWithNumericPrevention.create! name: "123"
85
+ found = ArticleWithNumericPrevention.friendly.find(record.slug)
86
+ assert_equal record.id, found.id
87
+ end
88
+ end
89
+
90
+ test "should resolve conflicts between multiple numeric slugs when treat_numeric_as_conflict is enabled" do
91
+ transaction do
92
+ record1 = ArticleWithNumericPrevention.create! name: "456"
93
+ record2 = ArticleWithNumericPrevention.create! name: "456"
94
+
95
+ refute_equal record1.slug, record2.slug
96
+ assert_match(/\A456-[0-9a-f-]{36}\z/, record1.slug)
97
+ assert_match(/\A456-[0-9a-f-]{36}\z/, record2.slug)
98
+ end
99
+ end
31
100
  end
metadata CHANGED
@@ -1,43 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: friendly_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Norman Clarke
8
8
  - Philip Arndt
9
- autorequire:
10
9
  bindir: bin
11
- cert_chain:
12
- - |
13
- -----BEGIN CERTIFICATE-----
14
- MIIEljCCAv6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBRMREwDwYDVQQDDAhydWJ5
15
- Z2VtczERMA8GCgmSJomT8ixkARkWAXAxFTATBgoJkiaJk/IsZAEZFgVhcm5kdDES
16
- MBAGCgmSJomT8ixkARkWAmlvMB4XDTIyMTExNTIyMjQzMFoXDTIzMTExNTIyMjQz
17
- MFowUTERMA8GA1UEAwwIcnVieWdlbXMxETAPBgoJkiaJk/IsZAEZFgFwMRUwEwYK
18
- CZImiZPyLGQBGRYFYXJuZHQxEjAQBgoJkiaJk/IsZAEZFgJpbzCCAaIwDQYJKoZI
19
- hvcNAQEBBQADggGPADCCAYoCggGBAMPq2bIEO+BmmBeuidSySK7xlL/LWBHzyDxw
20
- EMgWsHqJMDZYCZI4WoWbSTSSLrp5zPXLWN0hB23u3dxFp4RVygTTZkc8k05mteab
21
- fdREGgdcP+mY8/ASQSvb1VW6IM51Srgjy1SK0S5Qf3HAiQafFvRsxRkY0SWyth24
22
- ne/7HG667vHQ1+t0VFl8twupJE9S8p2zgX3eZBl2yRNm/kE5reUsOLvmS58Iri/X
23
- 9tnz0SGkzrKkim9OIByq7XkFLL3oaIyfbBVgOWilM5pvxj/xNuRH7EIM6aE3q0UZ
24
- xo7o9u9Iz2zApDEjejByPjxWAhLuP3v3bJyinRFE1rO47lEM/s6KM/6YooxvgYIN
25
- miYYFRtTj9nmKEMv6+h1mZ1/ZwqStTTRh/T90T65dcgsoqRd0JNvpNRjFrYH5cuj
26
- QZWMl/FE6AADm0GXa34ZiTQx3Wx2ctqJLFak8+imPwes90nCpiYmgaZpwBI+shjU
27
- AddbPDNq+EoxPMWTh0Er3w76fywOWQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1Ud
28
- DwQEAwIEsDAdBgNVHQ4EFgQUxRJaTQZmtkN8FKUWVHKc2riND18wHgYDVR0RBBcw
29
- FYETcnVieWdlbXNAcC5hcm5kdC5pbzAeBgNVHRIEFzAVgRNydWJ5Z2Vtc0BwLmFy
30
- bmR0LmlvMA0GCSqGSIb3DQEBCwUAA4IBgQBSRGMkZ2dvJ0LSjFz+rIt3G3AZMbKD
31
- tjaaQRuC9rOkrl3Rml6h9j7cHYiM0wkTjXneFNySc8jWmM/jKnxiiUfUK9r1XL4n
32
- 71tz39+MD2lIpLVVEQ69MIoUseppNUTCg0mNghSDYNwISMD/hoWwbJudBi56DbhE
33
- xkulLbw8qtcEE+iilIKibe+eJF4platKScsOA7d1AuilR1/S245UzeqwwyI52/xK
34
- dfoP928X9Tb/48+83lWUgAgCQOd6WdfCpgQ5H6R90lc8L7OfuDR/vgcmSOTsNVgG
35
- 1TC3b2FISS0p0qfZsiS7BXh+ARoBKLXsV1a7WR36X0dUpajvk+zzBGrFCdbW43Gx
36
- wmJzIksYnf9Ktg8Ux+FLcRBGw4qEIyWvqmS0obB1Hke68rTg0uNTFcKXsNw33XF5
37
- fw1cbj95g7OPe0feGK8+afXh/L38vx/hIIOGlUEZ+HaWL2Dki/7vRGvda8dfOpG5
38
- bJfaoyKbVsrK+gGKFJv860zsO8lg6BGLsUw=
39
- -----END CERTIFICATE-----
40
- date: 2022-11-15 00:00:00.000000000 Z
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
41
12
  dependencies:
42
13
  - !ruby/object:Gem::Dependency
43
14
  name: activerecord
@@ -101,14 +72,14 @@ dependencies:
101
72
  requirements:
102
73
  - - "~>"
103
74
  - !ruby/object:Gem::Version
104
- version: '1.1'
75
+ version: '2.1'
105
76
  type: :development
106
77
  prerelease: false
107
78
  version_requirements: !ruby/object:Gem::Requirement
108
79
  requirements:
109
80
  - - "~>"
110
81
  - !ruby/object:Gem::Version
111
- version: '1.1'
82
+ version: '2.1'
112
83
  - !ruby/object:Gem::Dependency
113
84
  name: yard
114
85
  requirement: !ruby/object:Gem::Requirement
@@ -170,7 +141,7 @@ description: FriendlyId is the "Swiss Army bulldozer" of slugging and permalink
170
141
  as if they were numeric ids.
171
142
  email:
172
143
  - norman@njclarke.com
173
- - p@arndt.io
144
+ - gems@p.arndt.io
174
145
  executables: []
175
146
  extensions: []
176
147
  extra_rdoc_files: []
@@ -179,6 +150,7 @@ files:
179
150
  - ".github/FUNDING.yml"
180
151
  - ".github/dependabot.yml"
181
152
  - ".github/stale.yml"
153
+ - ".github/workflows/release.yml"
182
154
  - ".github/workflows/test.yml"
183
155
  - ".gitignore"
184
156
  - ".yardopts"
@@ -190,12 +162,13 @@ files:
190
162
  - Rakefile
191
163
  - UPGRADING.md
192
164
  - bench.rb
193
- - certs/parndt.pem
194
165
  - friendly_id.gemspec
195
- - gemfiles/Gemfile.rails-5.2.rb
196
166
  - gemfiles/Gemfile.rails-6.0.rb
197
167
  - gemfiles/Gemfile.rails-6.1.rb
198
168
  - gemfiles/Gemfile.rails-7.0.rb
169
+ - gemfiles/Gemfile.rails-7.1.rb
170
+ - gemfiles/Gemfile.rails-7.2.rb
171
+ - gemfiles/Gemfile.rails-8.0.rb
199
172
  - guide.rb
200
173
  - lib/friendly_id.rb
201
174
  - lib/friendly_id/.gitattributes
@@ -243,7 +216,6 @@ homepage: https://github.com/norman/friendly_id
243
216
  licenses:
244
217
  - MIT
245
218
  metadata: {}
246
- post_install_message:
247
219
  rdoc_options: []
248
220
  require_paths:
249
221
  - lib
@@ -258,8 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
230
  - !ruby/object:Gem::Version
259
231
  version: '0'
260
232
  requirements: []
261
- rubygems_version: 3.3.7
262
- signing_key:
233
+ rubygems_version: 3.6.9
263
234
  specification_version: 4
264
235
  summary: A comprehensive slugging and pretty-URL plugin.
265
236
  test_files: []
checksums.yaml.gz.sig DELETED
Binary file
data/certs/parndt.pem DELETED
@@ -1,27 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIEljCCAv6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBRMREwDwYDVQQDDAhydWJ5
3
- Z2VtczERMA8GCgmSJomT8ixkARkWAXAxFTATBgoJkiaJk/IsZAEZFgVhcm5kdDES
4
- MBAGCgmSJomT8ixkARkWAmlvMB4XDTIyMTExNTIyMjQzMFoXDTIzMTExNTIyMjQz
5
- MFowUTERMA8GA1UEAwwIcnVieWdlbXMxETAPBgoJkiaJk/IsZAEZFgFwMRUwEwYK
6
- CZImiZPyLGQBGRYFYXJuZHQxEjAQBgoJkiaJk/IsZAEZFgJpbzCCAaIwDQYJKoZI
7
- hvcNAQEBBQADggGPADCCAYoCggGBAMPq2bIEO+BmmBeuidSySK7xlL/LWBHzyDxw
8
- EMgWsHqJMDZYCZI4WoWbSTSSLrp5zPXLWN0hB23u3dxFp4RVygTTZkc8k05mteab
9
- fdREGgdcP+mY8/ASQSvb1VW6IM51Srgjy1SK0S5Qf3HAiQafFvRsxRkY0SWyth24
10
- ne/7HG667vHQ1+t0VFl8twupJE9S8p2zgX3eZBl2yRNm/kE5reUsOLvmS58Iri/X
11
- 9tnz0SGkzrKkim9OIByq7XkFLL3oaIyfbBVgOWilM5pvxj/xNuRH7EIM6aE3q0UZ
12
- xo7o9u9Iz2zApDEjejByPjxWAhLuP3v3bJyinRFE1rO47lEM/s6KM/6YooxvgYIN
13
- miYYFRtTj9nmKEMv6+h1mZ1/ZwqStTTRh/T90T65dcgsoqRd0JNvpNRjFrYH5cuj
14
- QZWMl/FE6AADm0GXa34ZiTQx3Wx2ctqJLFak8+imPwes90nCpiYmgaZpwBI+shjU
15
- AddbPDNq+EoxPMWTh0Er3w76fywOWQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1Ud
16
- DwQEAwIEsDAdBgNVHQ4EFgQUxRJaTQZmtkN8FKUWVHKc2riND18wHgYDVR0RBBcw
17
- FYETcnVieWdlbXNAcC5hcm5kdC5pbzAeBgNVHRIEFzAVgRNydWJ5Z2Vtc0BwLmFy
18
- bmR0LmlvMA0GCSqGSIb3DQEBCwUAA4IBgQBSRGMkZ2dvJ0LSjFz+rIt3G3AZMbKD
19
- tjaaQRuC9rOkrl3Rml6h9j7cHYiM0wkTjXneFNySc8jWmM/jKnxiiUfUK9r1XL4n
20
- 71tz39+MD2lIpLVVEQ69MIoUseppNUTCg0mNghSDYNwISMD/hoWwbJudBi56DbhE
21
- xkulLbw8qtcEE+iilIKibe+eJF4platKScsOA7d1AuilR1/S245UzeqwwyI52/xK
22
- dfoP928X9Tb/48+83lWUgAgCQOd6WdfCpgQ5H6R90lc8L7OfuDR/vgcmSOTsNVgG
23
- 1TC3b2FISS0p0qfZsiS7BXh+ARoBKLXsV1a7WR36X0dUpajvk+zzBGrFCdbW43Gx
24
- wmJzIksYnf9Ktg8Ux+FLcRBGw4qEIyWvqmS0obB1Hke68rTg0uNTFcKXsNw33XF5
25
- fw1cbj95g7OPe0feGK8+afXh/L38vx/hIIOGlUEZ+HaWL2Dki/7vRGvda8dfOpG5
26
- bJfaoyKbVsrK+gGKFJv860zsO8lg6BGLsUw=
27
- -----END CERTIFICATE-----
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
@@ -1 +0,0 @@
1
- j搉g6hDŽ��� aQ�,��[�H�+P~M@ɈLO�z�!�TV�ٿ��>q|0T����ع|cDs1�`�����5�ϝd+��������\�:Ƶ���T��5��;�P��j]��h'�D�+�H-��@z}�,��WR) w���%�t�I�?L�4ɚ4K��뜂HI`4X