diff --git a/lib/friendly_id/base.rb b/lib/friendly_id/base.rb index 224bf666c..2844d1498 100644 --- a/lib/friendly_id/base.rb +++ b/lib/friendly_id/base.rb @@ -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 diff --git a/lib/friendly_id/initializer.rb b/lib/friendly_id/initializer.rb index b372367f4..1d8107c9a 100644 --- a/lib/friendly_id/initializer.rb +++ b/lib/friendly_id/initializer.rb @@ -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 diff --git a/lib/friendly_id/slugged.rb b/lib/friendly_id/slugged.rb index 9891ca2ca..930224e64 100644 --- a/lib/friendly_id/slugged.rb +++ b/lib/friendly_id/slugged.rb @@ -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 diff --git a/test/slugged_test.rb b/test/slugged_test.rb index 7e77cfcee..4a4943d14 100644 --- a/test/slugged_test.rb +++ b/test/slugged_test.rb @@ -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-" + 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 \ No newline at end of file +end