Skip to content

Commit

Permalink
Allow to set size limit for slug (#809)
Browse files Browse the repository at this point in the history
  • Loading branch information
avokhmin authored and parndt committed Aug 7, 2017
1 parent d765548 commit e873122
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 6 deletions.
3 changes: 3 additions & 0 deletions lib/friendly_id/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/friendly_id/initializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 58 additions & 5 deletions lib/friendly_id/slugged.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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?
Expand Down Expand Up @@ -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.
Expand All @@ -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
27 changes: 26 additions & 1 deletion test/slugged_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.