Class: FactoryBot::With::AssocInfo

Inherits:
Object
  • Object
show all
Defined in:
lib/factory_bot/with/assoc_info.rb

Overview

An association information of a FactoryBot factory.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#factory_namesSet<Symbol> (readonly)

Returns List of factory names to be considered compatible with this factory.

Returns:

  • (Set<Symbol>)

    List of factory names to be considered compatible with this factory



8
9
10
# File 'lib/factory_bot/with/assoc_info.rb', line 8

def factory_names
  @factory_names
end

#map{Symbol => Symbol} (readonly)

Returns a map from factory names to association names.

Returns:

  • ({Symbol => Symbol})

    a map from factory names to association names



10
11
12
# File 'lib/factory_bot/with/assoc_info.rb', line 10

def map
  @map
end

Class Method Details

.cache{Symbol => AssocInfo}

Returns:



101
# File 'lib/factory_bot/with/assoc_info.rb', line 101

def cache = @cache ||= {}

.exists?(factory_name) ⇒ Boolean

Parameters:

  • factory_name (Symbol)

Returns:

  • (Boolean)


65
66
67
# File 'lib/factory_bot/with/assoc_info.rb', line 65

def exists?(factory_name)
  !!cache.fetch(factory_name) { FactoryBot.factories.registered?(factory_name) }
end

.from_factory_bot_factory(factory_name) ⇒ AssocInfo

Parameters:

  • factory_name (Symbol)

Returns:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/factory_bot/with/assoc_info.rb', line 77

def from_factory_bot_factory(factory_name)
  unless FactoryBot.factories.registered?(factory_name)
    raise ArgumentError, "FactoryBot factory #{factory_name} is not defined"
  end

  factory = FactoryBot.factories.find(factory_name)

  # NOTE: We consider aliases to be incompatible with each other
  factory_names = Set[factory_name]
  map = {}
  while factory.is_a?(FactoryBot::Factory)
    factory_names << factory.name
    # Here, we use reverse_each to prioritize the upper association
    factory.with_traits(factory.defined_traits.map(&:name)).associations.reverse_each do |assoc|
      map[Array(assoc.factory)[0].to_sym] = assoc.name
    end

    factory = factory.__send__(:parent)
  end

  new(factory_names, map)
end

.get(factory_name) ⇒ AssocInfo

Parameters:

  • factory_name (Symbol)

Returns:



71
72
73
# File 'lib/factory_bot/with/assoc_info.rb', line 71

def get(factory_name)
  cache.fetch(factory_name) { cache[factory_name] = from_factory_bot_factory(factory_name) }
end

.perform_factory_name_completion(ancestors, partial_factory_name) ⇒ Symbol

Parameters:

  • ancestors (Array<Array(AssocInfo, Object)>)
  • partial_factory_name (Symbol)

Returns:

  • (Symbol)

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/factory_bot/with/assoc_info.rb', line 48

def perform_factory_name_completion(ancestors, partial_factory_name)
  ancestors.each do |(ancestor_assoc_info, _)|
    ancestor_assoc_info.factory_names.each do |ancestor_factory_name|
      factory_name = :"#{ancestor_factory_name}_#{partial_factory_name}"
      return factory_name if exists?(factory_name)
    end
  end

  # Attempt to resolve with the completed names, then attempt to resolve with the original name.
  # If we want to avoid completion, we should be able to simply use a factory such as build or create.
  return partial_factory_name if exists?(partial_factory_name)

  raise ArgumentError, "FactoryBot factory #{partial_factory_name} is not defined"
end

Instance Method Details

#perform_automatic_association_resolution(ancestors, dest) ⇒ Object

Parameters:

  • ancestors (Array<Array(AssocInfo, Object)>)
  • dest ({Symbol => Object})


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/factory_bot/with/assoc_info.rb', line 27

def perform_automatic_association_resolution(ancestors, dest)
  priorities = {}
  map.each do |factory_name, attribute|
    # skip if this attribute is explicitly specified
    next if dest.member?(attribute) && !priorities.member?(attribute)

    # closer ancestors have higher (lower integer) priority
    ancestor, priority = ancestors.each_with_index.find do |(ancestor_assoc_info, _), _|
      ancestor_assoc_info.factory_names.include?(factory_name)
    end
    next if !ancestor || priorities.fetch(attribute, Float::INFINITY) <= priority

    priorities[attribute] = priority
    dest[attribute] = ancestor[1]
  end
end