module WulffeldSlug module SlugInclude extend ActiveSupport::Concern included do class_eval do if self.slug_config[:mongo] field :slug end validates_format_of :slug, :with => /\A[[:alnum:]-]+\Z/, :allow_blank => false before_validation :on => :create do cb_make_slug end end end def to_param slug end # You may override this. def slug_unique?(slug) if new_record? !self.class.where(:slug => slug).first else if self.slug_config[:mongo] !self.class.where(:slug => slug).and(:_id.ne => self.id).first else !self.class.where(["slug = ? AND id <> ?", slug, self.id]).first end end end def cb_make_slug # NOTE: Uniqueness is not checked if slug is non-nil. You must ensure uniqueness yourself then. return unless slug.blank? slug_items = resolve_slug_items return if slug_items.blank? self.slug = to_unique_slug(slug_items) end def resolve_slug_items [*send(slug_config[:fields])].reject(&:nil?).map {|f| f.is_a?(String) ? f : send(f) }.flatten.reject(&:blank?) end def to_unique_slug(slug_items, n=nil) str_part = WulffeldSlug::PrepareString.new(slug_items, slug_config.merge(:kinds => (respond_to?(:slug_kinds) ? [*slug_kinds] : nil))).slug str_part = self.id.to_s if str_part.blank? && !self.new_record? loop do slug = [str_part, n].reject(&:blank?).join('-') return slug if !slug.blank? && slug_unique?(slug) if str_part.blank? && slug_config[:blank_generator] str_part = self.method(slug_config[:blank_generator]).call else # First loop we do some optimistic searching. n ||= find_n_optimistically(str_part) || 0 n += 1 end end end def find_n_optimistically(str_part) # There's often a difference between adding a number to a slug where the string is present # and one where we only have the number for the final slug. # :blank_loop_start and :blank_loop_chunk helps speed up the looping on blank slugs. if str_part.blank? b = slug_config[:blank_loop_start] else b = slug_config[:loop_start] end last_used_postfix = nil loop do slug = [str_part, b].reject(&:blank?).join('-') if slug_unique?(slug) || b < 1 # No postfixes used yet. return 0 if b < 1 else return b end if str_part.blank? && slug_config[:blank_loop_chunk] && b - slug_config[:blank_loop_chunk] > slug_config[:blank_loop_chunk] b -= slug_config[:blank_loop_chunk] else b /= 2 end end end end end