Permalink
Browse files

Allow to set size limit for slug (#809)

  • Loading branch information...
1 parent d765548 commit e8731227f89986c31aa95315567613de164aefda @avokhmin avokhmin committed with parndt Aug 7, 2017
Showing with 91 additions and 6 deletions.
  1. +3 −0 lib/friendly_id/base.rb
  2. +4 −0 lib/friendly_id/initializer.rb
  3. +58 −5 lib/friendly_id/slugged.rb
  4. +26 −1 test/slugged_test.rb
View
@@ -178,6 +178,9 @@ module Base
# Configures the name of the column where FriendlyId will store the slug.
# Defaults to `:slug`.
#
+ # @option options [Integer] :slug_limit Available when using `:slugged`.
+ # Configures the limit of the slug. This option has no default value.
+ #
# @option options [Symbol] :slug_generator_class Available when using `:slugged`.
# Sets the class used to generate unique slugs. You should not specify this
# unless you're doing some extensive hacking on FriendlyId. Defaults to
@@ -52,6 +52,10 @@
#
# config.slug_column = 'slug'
#
+ # By default, slug has no size limit, but you can change it if you wish.
+ #
+ # config.slug_limit = 255
+ #
# When FriendlyId can not generate a unique ID from your base method, it appends
# a UUID, separated by a single dash. You can configure the character used as the
# separator. If you're upgrading from FriendlyId 4, you may wish to replace this
View
@@ -288,7 +288,9 @@ def self.included(model_class)
# @param [#to_s] value The value used as the basis of the slug.
# @return The candidate slug text, without a sequence.
def normalize_friendly_id(value)
- value.to_s.parameterize
+ value = value.to_s.parameterize
+ value = value[0...friendly_id_config.slug_limit] if friendly_id_config.slug_limit
+ value
end
# Whether to generate a new slug.
@@ -299,10 +301,56 @@ def should_generate_new_friendly_id?
send(friendly_id_config.slug_column).nil? && !send(friendly_id_config.base).nil?
end
+ # Public: Resolve conflicts.
+ #
+ # This method adds UUID to first candidate and truncates (if `slug_limit` is set).
+ #
+ # Examples:
+ #
+ # resolve_friendly_id_conflict(['12345'])
+ # # => '12345-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ #
+ # FriendlyId.defaults { |config| config.slug_limit = 40 }
+ # resolve_friendly_id_conflict(['12345'])
+ # # => '123-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
+ #
+ # candidates - the Array with candidates.
+ #
+ # Returns the String with new slug.
def resolve_friendly_id_conflict(candidates)
- [candidates.first, SecureRandom.uuid].compact.join(friendly_id_config.sequence_separator)
+ uuid = SecureRandom.uuid
+ [
+ apply_slug_limit(candidates.first, uuid),
+ uuid
+ ].compact.join(friendly_id_config.sequence_separator)
end
+ # Private: Apply slug limit to candidate.
+ #
+ # candidate - the String with candidate.
+ # uuid - the String with UUID.
+ #
+ # Return the String with truncated candidate.
+ def apply_slug_limit(candidate, uuid)
+ return candidate unless candidate && friendly_id_config.slug_limit
+
+ candidate[0...candidate_limit(uuid)]
+ end
+ private :apply_slug_limit
+
+ # Private: Get max length of candidate.
+ #
+ # uuid - the String with UUID.
+ #
+ # Returns the Integer with max length.
+ def candidate_limit(uuid)
+ [
+ friendly_id_config.slug_limit - uuid.size - friendly_id_config.sequence_separator.size,
+ 0
+ ].max
+ end
+ private :candidate_limit
+
# Sets the slug.
def set_slug(normalized_slug = nil)
if should_generate_new_friendly_id?
@@ -334,11 +382,11 @@ def unset_slug_if_invalid
end
private :unset_slug_if_invalid
- # This module adds the `:slug_column`, and `:sequence_separator`, and
- # `:slug_generator_class` configuration options to
+ # This module adds the `:slug_column`, and `:slug_limit`, and `:sequence_separator`,
+ # and `:slug_generator_class` configuration options to
# {FriendlyId::Configuration FriendlyId::Configuration}.
module Configuration
- attr_writer :slug_column, :sequence_separator
+ attr_writer :slug_column, :slug_limit, :sequence_separator
attr_accessor :slug_generator_class
# Makes FriendlyId use the slug column for querying.
@@ -361,6 +409,11 @@ def sequence_separator
def slug_column
@slug_column ||= defaults[:slug_column]
end
+
+ # The limit that will be used for slug.
+ def slug_limit
+ @slug_limit ||= defaults[:slug_limit]
+ end
end
end
end
View
@@ -271,6 +271,31 @@ def self.name
end
+class SlugLimitTest < TestCaseClass
+
+ include FriendlyId::Test
+
+ class Journalist < ActiveRecord::Base
+ extend FriendlyId
+ friendly_id :name, :use => :slugged, :slug_limit => 40
+ end
+
+ def model_class
+ Journalist
+ end
+
+ test "should limit slug size" do
+ transaction do
+ m1 = model_class.create! :name => 'a' * 50
+ assert_equal m1.slug, 'a' * 40
+ m2 = model_class.create! :name => m1.name
+ m2.save!
+ # "aaa-<uid>"
+ assert_match(/\Aa{3}\-/, m2.slug)
+ end
+ end
+end
+
class DefaultScopeTest < TestCaseClass
include FriendlyId::Test
@@ -424,4 +449,4 @@ class Novel < ActiveRecord::Base
assert_equal novel.id.to_s, novel.to_param
end
end
-end
+end

0 comments on commit e873122

Please sign in to comment.