Module: FactoryBurgers::Cheating

Defined in:
lib/factory_burgers/cheating.rb

Overview

This module contrains utilities to manipulate sequences that might fail for usage in development environments that do not roll back db transactions for factory creations.

Class Method Summary collapse

Class Method Details

.advance_sequence(name, klass, column, sql: nil, regex: nil) ⇒ Object

Find the highest value of a sequence in the database and advance the sequence past it to avoid uniqueness violations. This is by no means foolproof nor performant; use it with care. There isn’t a good way to access the iterator state; instead, we measure how far we must advance the sequence and call ‘generate` that many times. This works well for sequential iterations, but more complex sequences might break this.



29
30
31
32
33
34
35
36
# File 'lib/factory_burgers/cheating.rb', line 29

def advance_sequence(name, klass, column, sql: nil, regex: nil)
  sequence = FactoryBot::Internal.sequences.find(name)
  sql ||= sql_condition(sequence, column)
  regex ||= regex_pattern(sequence)

  highest = find_highest_index_value(klass, column, sql, regex) or return nil
  highest&.times { FactoryBot.generate name }
end

.discover_sequences(factory) ⇒ Object

Given a factory, discover what sequences are used in its attributes. Since factories’ attributes are defined as blocks, there is no way to know which blocks use sequences without executing the block. We do this with SequenceCheater to take notes without actually using the seqence itself.



12
13
14
15
16
17
18
19
20
# File 'lib/factory_burgers/cheating.rb', line 12

def discover_sequences(factory)
  cheater = SequenceCheater.new
  attributes = factory.definition.attributes
  attributes.map do |attr|
    block = attr.instance_variable_get(:@block)
    cheater.instance_eval(&block)
  end
  return cheater.sequence_names
end

.find_highest_index_value(klass, column, sql, regex) ⇒ Object



38
39
40
41
# File 'lib/factory_burgers/cheating.rb', line 38

def find_highest_index_value(klass, column, sql, regex)
  matches = klass.where(sql).pluck(column).grep(regex)
  return matches.map { |value| value =~ regex && Regexp.last_match(1) }.map(&:to_i).max
end

.regex_pattern(sequence, numeric: true) ⇒ Object

For a sequence defined with { |ii| “foo#ii” }, ‘sql_condition` returns the regex /foo(d+)/



65
66
67
68
69
70
# File 'lib/factory_burgers/cheating.rb', line 65

def regex_pattern(sequence, numeric: true)
  wildcard = numeric ? "\\d" : "."
  proc = sequence.instance_variable_get(:@proc)
  injector = SequenceInjector.new("(#{wildcard}+)")
  return Regexp.new(proc.call(injector))
end

.sql_condition(sequence, column) ⇒ Object

For a sequence defined with { |ii| “foo#ii” }, ‘sql_condition` returns the SQL fragemnt “<name> like ’foo%‘” TODO: does this work in pg, sqlite? TODO: support mongo as well



54
55
56
57
58
59
60
61
# File 'lib/factory_burgers/cheating.rb', line 54

def sql_condition(sequence, column)
  # This proc is defined by the block used in the sequence definition
  # This may be fragile, but may also be out only option
  proc = sequence.instance_variable_get(:@proc)
  injector = SequenceInjector.new("%")
  sql_value = proc.call(injector)
  return "#{column} like '#{sql_value}'"
end