Class: FriendlyId::SequentiallySlugged::Calculator

Inherits:
Object
  • Object
show all
Defined in:
lib/friendly_id/sequentially_slugged/calculator.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope, slug, slug_column, sequence_separator, base_class) ⇒ Calculator

Returns a new instance of Calculator.



6
7
8
9
10
11
12
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 6

def initialize(scope, slug, slug_column, sequence_separator, base_class)
  @scope = scope
  @slug = slug
  table_name = scope.connection.quote_table_name(base_class.arel_table.name)
  @slug_column = "#{table_name}.#{scope.connection.quote_column_name(slug_column)}"
  @sequence_separator = sequence_separator
end

Instance Attribute Details

#scopeObject

Returns the value of attribute scope.



4
5
6
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 4

def scope
  @scope
end

#sequence_separatorObject

Returns the value of attribute sequence_separator.



4
5
6
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 4

def sequence_separator
  @sequence_separator
end

#slugObject

Returns the value of attribute slug.



4
5
6
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 4

def slug
  @slug
end

#slug_columnObject

Returns the value of attribute slug_column.



4
5
6
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 4

def slug_column
  @slug_column
end

Instance Method Details

#conflict_queryObject (private)



20
21
22
23
24
25
26
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 20

def conflict_query
  base = "#{slug_column} = ? OR #{slug_column} LIKE ?"
  # Awful hack for SQLite3, which does not pick up '\' as the escape character
  # without this.
  base << " ESCAPE '\\'" if /sqlite/i.match?(scope.connection.adapter_name)
  base
end

#last_sequence_numberObject (private)



32
33
34
35
36
37
38
39
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 32

def last_sequence_number
  # Reject slug_conflicts that doesn't come from the first_candidate
  # Map all sequence numbers and take the maximum
  slug_conflicts
    .reject { |slug_conflict| !regexp.match(slug_conflict) }
    .map { |slug_conflict| regexp.match(slug_conflict)[1].to_i }
    .max
end

#next_sequence_numberObject (private)



28
29
30
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 28

def next_sequence_number
  last_sequence_number ? last_sequence_number + 1 : 2
end

#next_slugObject



14
15
16
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 14

def next_slug
  slug + sequence_separator + next_sequence_number.to_s
end

#ordering_queryObject (private)

Return the unnumbered (shortest) slug first, followed by the numbered ones in ascending order.



43
44
45
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 43

def ordering_query
  "#{sql_length}(#{slug_column}) ASC, #{slug_column} ASC"
end

#regexpObject (private)



47
48
49
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 47

def regexp
  /#{slug}#{sequence_separator}(\d+)\z/
end

#sequential_slug_matcherObject (private)



51
52
53
54
55
56
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 51

def sequential_slug_matcher
  # Underscores (matching a single character) and percent signs (matching
  # any number of characters) need to be escaped. While this looks like
  # an excessive number of backslashes, it is correct.
  "#{slug}#{sequence_separator}".gsub(/[_%]/, '\\\\\&') + "%"
end

#slug_conflictsObject (private)



58
59
60
61
62
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 58

def slug_conflicts
  scope
    .where(conflict_query, slug, sequential_slug_matcher)
    .order(Arel.sql(ordering_query)).pluck(Arel.sql(slug_column))
end

#sql_lengthObject (private)



64
65
66
# File 'lib/friendly_id/sequentially_slugged/calculator.rb', line 64

def sql_length
  /sqlserver/i.match?(scope.connection.adapter_name) ? "LEN" : "LENGTH"
end