friendly_id 5.4.2 → 5.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/test.yml +38 -25
  5. data/.yardopts +2 -0
  6. data/Changelog.md +10 -0
  7. data/Gemfile +9 -13
  8. data/README.md +21 -0
  9. data/Rakefile +24 -27
  10. data/bench.rb +30 -27
  11. data/certs/parndt.pem +25 -23
  12. data/friendly_id.gemspec +26 -29
  13. data/gemfiles/Gemfile.rails-5.2.rb +11 -16
  14. data/gemfiles/Gemfile.rails-6.0.rb +11 -16
  15. data/gemfiles/Gemfile.rails-6.1.rb +22 -0
  16. data/gemfiles/Gemfile.rails-7.0.rb +22 -0
  17. data/guide.rb +13 -6
  18. data/lib/friendly_id/base.rb +59 -60
  19. data/lib/friendly_id/candidates.rb +9 -11
  20. data/lib/friendly_id/configuration.rb +6 -7
  21. data/lib/friendly_id/finder_methods.rb +26 -11
  22. data/lib/friendly_id/finders.rb +66 -66
  23. data/lib/friendly_id/history.rb +62 -63
  24. data/lib/friendly_id/initializer.rb +4 -4
  25. data/lib/friendly_id/migration.rb +6 -6
  26. data/lib/friendly_id/object_utils.rb +2 -2
  27. data/lib/friendly_id/reserved.rb +30 -32
  28. data/lib/friendly_id/scoped.rb +99 -102
  29. data/lib/friendly_id/sequentially_slugged/calculator.rb +69 -0
  30. data/lib/friendly_id/sequentially_slugged.rb +17 -64
  31. data/lib/friendly_id/simple_i18n.rb +78 -69
  32. data/lib/friendly_id/slug.rb +1 -2
  33. data/lib/friendly_id/slug_generator.rb +1 -3
  34. data/lib/friendly_id/slugged.rb +236 -238
  35. data/lib/friendly_id/version.rb +1 -1
  36. data/lib/friendly_id.rb +47 -49
  37. data/lib/generators/friendly_id_generator.rb +9 -9
  38. data/test/base_test.rb +10 -13
  39. data/test/benchmarks/finders.rb +28 -26
  40. data/test/benchmarks/object_utils.rb +13 -13
  41. data/test/candidates_test.rb +17 -18
  42. data/test/configuration_test.rb +7 -11
  43. data/test/core_test.rb +1 -2
  44. data/test/databases.yml +4 -3
  45. data/test/finders_test.rb +52 -5
  46. data/test/generator_test.rb +16 -26
  47. data/test/helper.rb +31 -24
  48. data/test/history_test.rb +70 -74
  49. data/test/numeric_slug_test.rb +4 -4
  50. data/test/object_utils_test.rb +0 -2
  51. data/test/reserved_test.rb +9 -11
  52. data/test/schema.rb +5 -4
  53. data/test/scoped_test.rb +18 -20
  54. data/test/sequentially_slugged_test.rb +65 -50
  55. data/test/shared.rb +15 -16
  56. data/test/simple_i18n_test.rb +22 -12
  57. data/test/slugged_test.rb +102 -121
  58. data/test/sti_test.rb +19 -21
  59. data.tar.gz.sig +0 -0
  60. metadata +37 -32
  61. metadata.gz.sig +0 -0
@@ -1,3 +1,5 @@
1
+ require_relative "sequentially_slugged/calculator"
2
+
1
3
  module FriendlyId
2
4
  module SequentiallySlugged
3
5
  def self.setup(model_class)
@@ -7,71 +9,14 @@ module FriendlyId
7
9
  def resolve_friendly_id_conflict(candidate_slugs)
8
10
  candidate = candidate_slugs.first
9
11
  return if candidate.nil?
10
- SequentialSlugCalculator.new(scope_for_slug_generator,
11
- candidate,
12
- friendly_id_config.slug_column,
13
- friendly_id_config.sequence_separator,
14
- slug_base_class).next_slug
15
- end
16
-
17
- class SequentialSlugCalculator
18
- attr_accessor :scope, :slug, :slug_column, :sequence_separator
19
-
20
- def initialize(scope, slug, slug_column, sequence_separator, base_class)
21
- @scope = scope
22
- @slug = slug
23
- table_name = scope.connection.quote_table_name(base_class.arel_table.name)
24
- @slug_column = "#{table_name}.#{scope.connection.quote_column_name(slug_column)}"
25
- @sequence_separator = sequence_separator
26
- end
27
-
28
- def next_slug
29
- slug + sequence_separator + next_sequence_number.to_s
30
- end
31
-
32
- private
33
-
34
- def next_sequence_number
35
- last_sequence_number ? last_sequence_number + 1 : 2
36
- end
37
-
38
- def last_sequence_number
39
- regexp = /#{slug}#{sequence_separator}(\d+)\z/
40
- # Reject slug_conflicts that doesn't come from the first_candidate
41
- # Map all sequence numbers and take the maximum
42
- slug_conflicts.reject{ |slug_conflict| !regexp.match(slug_conflict) }.map do |slug_conflict|
43
- regexp.match(slug_conflict)[1].to_i
44
- end.max
45
- end
46
12
 
47
- def slug_conflicts
48
- scope.
49
- where(conflict_query, slug, sequential_slug_matcher).
50
- order(Arel.sql(ordering_query)).pluck(Arel.sql(slug_column))
51
- end
52
-
53
- def conflict_query
54
- base = "#{slug_column} = ? OR #{slug_column} LIKE ?"
55
- # Awful hack for SQLite3, which does not pick up '\' as the escape character
56
- # without this.
57
- base << " ESCAPE '\\'" if scope.connection.adapter_name =~ /sqlite/i
58
- base
59
- end
60
-
61
- def sequential_slug_matcher
62
- # Underscores (matching a single character) and percent signs (matching
63
- # any number of characters) need to be escaped. While this looks like
64
- # an excessive number of backslashes, it is correct.
65
- "#{slug}#{sequence_separator}".gsub(/[_%]/, '\\\\\&') + '%'
66
- end
67
-
68
- # Return the unnumbered (shortest) slug first, followed by the numbered ones
69
- # in ascending order.
70
- def ordering_query
71
- length_command = "LENGTH"
72
- length_command = "LEN" if scope.connection.adapter_name =~ /sqlserver/i
73
- "#{length_command}(#{slug_column}) ASC, #{slug_column} ASC"
74
- end
13
+ Calculator.new(
14
+ scope_for_slug_generator,
15
+ candidate,
16
+ slug_column,
17
+ friendly_id_config.sequence_separator,
18
+ slug_base_class
19
+ ).next_slug
75
20
  end
76
21
 
77
22
  private
@@ -83,5 +28,13 @@ module FriendlyId
83
28
  self.class.base_class
84
29
  end
85
30
  end
31
+
32
+ def slug_column
33
+ if friendly_id_config.uses?(:history)
34
+ :slug
35
+ else
36
+ friendly_id_config.slug_column
37
+ end
38
+ end
86
39
  end
87
40
  end
@@ -1,75 +1,78 @@
1
1
  require "i18n"
2
2
 
3
3
  module FriendlyId
4
-
5
- =begin
6
-
7
- ## Translating Slugs Using Simple I18n
8
-
9
- The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to
10
- FriendlyId.
11
-
12
- In order to use this module, your model must have a slug column for each locale.
13
- By default FriendlyId looks for columns named, for example, "slug_en",
14
- "slug_es", etc. The first part of the name can be configured by passing the
15
- `:slug_column` option if you choose. Note that the column for the default locale
16
- must also include the locale in its name.
17
-
18
- This module is most suitable to applications that need to support few locales.
19
- If you need to support two or more locales, you may wish to use the
20
- friendly_id_globalize gem instead.
21
-
22
- ### Example migration
23
-
24
- def self.up
25
- create_table :posts do |t|
26
- t.string :title
27
- t.string :slug_en
28
- t.string :slug_es
29
- t.text :body
30
- end
31
- add_index :posts, :slug_en
32
- add_index :posts, :slug_es
33
- end
34
-
35
- ### Finds
36
-
37
- Finds will take into consideration the current locale:
38
-
39
- I18n.locale = :es
40
- Post.friendly.find("la-guerra-de-las-galaxias")
41
- I18n.locale = :en
42
- Post.friendly.find("star-wars")
43
-
44
- To find a slug by an explicit locale, perform the find inside a block
45
- passed to I18n's `with_locale` method:
46
-
47
- I18n.with_locale(:es) do
48
- Post.friendly.find("la-guerra-de-las-galaxias")
49
- end
50
-
51
- ### Creating Records
52
-
53
- When new records are created, the slug is generated for the current locale only.
54
-
55
- ### Translating Slugs
56
-
57
- To translate an existing record's friendly_id, use
58
- {FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug
59
- you add is properly escaped, transliterated and sequenced:
60
-
61
- post = Post.create :name => "Star Wars"
62
- post.set_friendly_id("La guerra de las galaxias", :es)
63
-
64
- If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the
65
- current locale:
66
-
67
- I18n.with_locale(:es) do
68
- post.set_friendly_id("La guerra de las galaxias")
69
- end
70
- =end
4
+ # @guide begin
5
+ #
6
+ # ## Translating Slugs Using Simple I18n
7
+ #
8
+ # The {FriendlyId::SimpleI18n SimpleI18n} module adds very basic i18n support to
9
+ # FriendlyId.
10
+ #
11
+ # In order to use this module, your model must have a slug column for each locale.
12
+ # By default FriendlyId looks for columns named, for example, "slug_en",
13
+ # "slug_es", "slug_pt_br", etc. The first part of the name can be configured by
14
+ # passing the `:slug_column` option if you choose. Note that the column for the
15
+ # default locale must also include the locale in its name.
16
+ #
17
+ # This module is most suitable to applications that need to support few locales.
18
+ # If you need to support two or more locales, you may wish to use the
19
+ # friendly_id_globalize gem instead.
20
+ #
21
+ # ### Example migration
22
+ #
23
+ # def self.up
24
+ # create_table :posts do |t|
25
+ # t.string :title
26
+ # t.string :slug_en
27
+ # t.string :slug_es
28
+ # t.string :slug_pt_br
29
+ # t.text :body
30
+ # end
31
+ # add_index :posts, :slug_en
32
+ # add_index :posts, :slug_es
33
+ # add_index :posts, :slug_pt_br
34
+ # end
35
+ #
36
+ # ### Finds
37
+ #
38
+ # Finds will take into consideration the current locale:
39
+ #
40
+ # I18n.locale = :es
41
+ # Post.friendly.find("la-guerra-de-las-galaxias")
42
+ # I18n.locale = :en
43
+ # Post.friendly.find("star-wars")
44
+ # I18n.locale = :"pt-BR"
45
+ # Post.friendly.find("guerra-das-estrelas")
46
+ #
47
+ # To find a slug by an explicit locale, perform the find inside a block
48
+ # passed to I18n's `with_locale` method:
49
+ #
50
+ # I18n.with_locale(:es) do
51
+ # Post.friendly.find("la-guerra-de-las-galaxias")
52
+ # end
53
+ #
54
+ # ### Creating Records
55
+ #
56
+ # When new records are created, the slug is generated for the current locale only.
57
+ #
58
+ # ### Translating Slugs
59
+ #
60
+ # To translate an existing record's friendly_id, use
61
+ # {FriendlyId::SimpleI18n::Model#set_friendly_id}. This will ensure that the slug
62
+ # you add is properly escaped, transliterated and sequenced:
63
+ #
64
+ # post = Post.create :name => "Star Wars"
65
+ # post.set_friendly_id("La guerra de las galaxias", :es)
66
+ #
67
+ # If you don't pass in a locale argument, FriendlyId::SimpleI18n will just use the
68
+ # current locale:
69
+ #
70
+ # I18n.with_locale(:es) do
71
+ # post.set_friendly_id("La guerra de las galaxias")
72
+ # end
73
+ #
74
+ # @guide end
71
75
  module SimpleI18n
72
-
73
76
  # FriendlyId::Config.use will invoke this method when present, to allow
74
77
  # loading dependent modules prior to overriding them when necessary.
75
78
  def self.setup(model_class)
@@ -98,7 +101,13 @@ current locale:
98
101
 
99
102
  module Configuration
100
103
  def slug_column
101
- "#{super}_#{I18n.locale}"
104
+ "#{super}_#{locale_suffix}"
105
+ end
106
+
107
+ private
108
+
109
+ def locale_suffix
110
+ I18n.locale.to_s.underscore
102
111
  end
103
112
  end
104
113
  end
@@ -3,7 +3,7 @@ module FriendlyId
3
3
  #
4
4
  # @see FriendlyId::History
5
5
  class Slug < ActiveRecord::Base
6
- belongs_to :sluggable, :polymorphic => true
6
+ belongs_to :sluggable, polymorphic: true
7
7
 
8
8
  def sluggable
9
9
  sluggable_type.constantize.unscoped { super }
@@ -12,6 +12,5 @@ module FriendlyId
12
12
  def to_param
13
13
  slug
14
14
  end
15
-
16
15
  end
17
16
  end
@@ -2,7 +2,6 @@ module FriendlyId
2
2
  # The default slug generator offers functionality to check slug candidates for
3
3
  # availability.
4
4
  class SlugGenerator
5
-
6
5
  def initialize(scope, config)
7
6
  @scope = scope
8
7
  @config = config
@@ -17,9 +16,8 @@ module FriendlyId
17
16
  end
18
17
 
19
18
  def generate(candidates)
20
- candidates.each {|c| return c if available?(c)}
19
+ candidates.each { |c| return c if available?(c) }
21
20
  nil
22
21
  end
23
-
24
22
  end
25
23
  end