Class: Mongoid::Slug::UniqueSlug

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/mongoid/slug/unique_slug.rb

Defined Under Namespace

Classes: SlugState

Constant Summary collapse

MUTEX_FOR_SLUG =
Mutex.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model) ⇒ UniqueSlug

Returns a new instance of UniqueSlug.



73
74
75
76
77
# File 'lib/mongoid/slug/unique_slug.rb', line 73

def initialize(model)
  @model = model
  @_slug = ''
  @state = nil
end

Instance Attribute Details

#_slugObject (readonly)

Returns the value of attribute _slug.



66
67
68
# File 'lib/mongoid/slug/unique_slug.rb', line 66

def _slug
  @_slug
end

#modelObject (readonly)

Returns the value of attribute model.



66
67
68
# File 'lib/mongoid/slug/unique_slug.rb', line 66

def model
  @model
end

Instance Method Details

#escaped_patternObject



129
130
131
# File 'lib/mongoid/slug/unique_slug.rb', line 129

def escaped_pattern
  "^#{Regexp.escape(@_slug)}(?:-(\\d+))?$"
end

#find_unique(attempt = nil) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/mongoid/slug/unique_slug.rb', line 89

def find_unique(attempt = nil)
  MUTEX_FOR_SLUG.synchronize do
    @_slug = if attempt
               attempt.to_url
             else
               slug_url_builder.call(model)
             end

    @_slug = @_slug[0...slug_max_length] if slug_max_length

    where_hash = {}
    where_hash[:_slugs.all] = [regex_for_slug]
    where_hash[:_id.ne]     = model._id

    if (scope = slug_scope) && reflect_on_association(scope).nil?
      # scope is not an association, so it's scoped to a local field
      # (e.g. an association id in a denormalized db design)
      where_hash[scope] = model.try(:read_attribute, scope)
    end

    where_hash[:_type] = model.try(:read_attribute, :_type) if slug_by_model_type

    @state = SlugState.new @_slug, uniqueness_scope.unscoped.where(where_hash), escaped_pattern

    # do not allow a slug that can be interpreted as the current document id
    @state.include_slug unless model.class.look_like_slugs?([@_slug])

    # make sure that the slug is not equal to a reserved word
    @state.include_slug if slug_reserved_words.any? { |word| word === @_slug } # rubocop:disable Style/CaseEquality

    # only look for a new unique slug if the existing slugs contains the current slug
    # - e.g if the slug 'foo-2' is taken, but 'foo' is available, the user can use 'foo'.
    if @state.slug_included?
      highest = @state.highest_existing_counter
      @_slug += "-#{highest.succ}"
    end
    @_slug
  end
end

#metadataObject



79
80
81
82
83
84
85
86
87
# File 'lib/mongoid/slug/unique_slug.rb', line 79

def 
  if @model.respond_to?(:_association)
    @model.send(:_association)
  elsif @model.respond_to?(:relation_metadata)
    @model.
  else
    @model.
  end
end

#regex_for_slugObject

Regular expression that matches slug, slug-1, … slug-n If slug_name field was indexed, MongoDB will utilize that index to match /^…/ pattern. Use Regexp::Raw to avoid the multiline option when querying the server.



137
138
139
140
141
142
143
# File 'lib/mongoid/slug/unique_slug.rb', line 137

def regex_for_slug
  if embedded?
    Regexp.new(escaped_pattern)
  else
    BSON::Regexp::Raw.new(escaped_pattern)
  end
end

#uniqueness_scopeObject



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/mongoid/slug/unique_slug.rb', line 145

def uniqueness_scope
  if slug_scope && ( = reflect_on_association(slug_scope))

    parent = model.send(.name)

    # Make sure doc is actually associated with something, and that
    # some referenced docs have been persisted to the parent
    #
    # TODO: we need better reflection for reference associations,
    # like association_name instead of forcing collection_name here
    # -- maybe in the forthcoming Mongoid refactorings?
    inverse = .inverse_of || collection_name
    return parent.respond_to?(inverse) ? parent.send(inverse) : model.class
  end

  if embedded?
     = reflect_on_all_association(:embedded_in)[0]
    return model._parent.send(.inverse_of || self..name)
  end

  # unless embedded or slug scope, return the deepest document superclass
  appropriate_class = model.class
  appropriate_class = appropriate_class.superclass while appropriate_class.superclass.include?(Mongoid::Document)
  appropriate_class
end