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.



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

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

Instance Attribute Details

#_slugObject (readonly)

Returns the value of attribute _slug.



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

def _slug
  @_slug
end

#modelObject (readonly)

Returns the value of attribute model.



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

def model
  @model
end

Instance Method Details

#escaped_patternObject



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

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

#find_unique(attempt = nil) ⇒ Object



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
125
126
127
128
# File 'lib/mongoid/slug/unique_slug.rb', line 88

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

    Array(slug_scope).each do |individual_scope|
      next unless reflect_on_association(individual_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[individual_scope] = model.try(:read_attribute, individual_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



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

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.



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

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

#uniqueness_scopeObject



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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/mongoid/slug/unique_slug.rb', line 146

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

  # If slug_scope is present, we need to handle whether it's a single scope or multiple scopes.
  if slug_scope
    # We'll track individual scope results in an array.
    scope_results = []

    Array(slug_scope).each do |individual_scope|
      next unless ( = reflect_on_association(individual_scope))

      # For each scope, we identify its association metadata and fetch the parent record.
      parent = model.send(.name)

      # It's important to handle nil cases if the parent record doesn't exist.
      if parent.nil?
        # You might want to handle this scenario differently based on your application's logic.
        next
      end

      # 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
      next unless parent.respond_to?(inverse)

      # Add the associated records of the parent (based on the inverse) to our results.
      scope_results << parent.send(inverse)
    end

    # After iterating through all scopes, we need to decide how to combine the results (if there are multiple).
    # This part depends on how your application should treat multiple scopes.
    # Here, we'll simply return the first non-empty scope result as an example.
    scope_results.each do |result|
      return result if result.present? # or any other logic for selecting among multiple scope results
    end

    # If we reach this point, it means no valid parent scope was found (all were nil or didn't match the
    # conditions).
    # You might want to raise an error, return a default scope, or handle this scenario based on your
    # application's logic.
    # For this example, we're returning the model's class as a default.
    return model.class
  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