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.



70
71
72
73
74
# File 'lib/mongoid/slug/unique_slug.rb', line 70

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

Instance Attribute Details

#_slugObject (readonly)

Returns the value of attribute _slug.



63
64
65
# File 'lib/mongoid/slug/unique_slug.rb', line 63

def _slug
  @_slug
end

#modelObject (readonly)

Returns the value of attribute model.



63
64
65
# File 'lib/mongoid/slug/unique_slug.rb', line 63

def model
  @model
end

Instance Method Details

#escaped_patternObject



126
127
128
# File 'lib/mongoid/slug/unique_slug.rb', line 126

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

#find_unique(attempt = nil) ⇒ Object



86
87
88
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
# File 'lib/mongoid/slug/unique_slug.rb', line 86

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 }

    # 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



76
77
78
79
80
81
82
83
84
# File 'lib/mongoid/slug/unique_slug.rb', line 76

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.



134
135
136
137
138
139
140
# File 'lib/mongoid/slug/unique_slug.rb', line 134

def regex_for_slug
  if embedded? || Mongoid::Compatibility::Version.mongoid3? || Mongoid::Compatibility::Version.mongoid4?
    Regexp.new(escaped_pattern)
  else
    BSON::Regexp::Raw.new(escaped_pattern)
  end
end

#uniqueness_scopeObject



142
143
144
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
170
# File 'lib/mongoid/slug/unique_slug.rb', line 142

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?
     = if Mongoid::Compatibility::Version.mongoid7_or_newer?
                        reflect_on_all_association(:embedded_in)[0]
                      else
                        reflect_on_all_associations(:embedded_in)[0]
                      end
    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