1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 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 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 202 203 204 205 206 207 208 209
|
module FactoryBot
# @api private
class Definition
attr_reader :defined_traits, :declarations, :name, :registered_enums
attr_accessor :klass
def initialize(name, base_traits = [])
@name = name
@declarations = DeclarationList.new(name)
@callbacks = []
@defined_traits = Set.new
@registered_enums = []
@to_create = nil
@base_traits = base_traits
@additional_traits = []
@constructor = nil
@attributes = nil
@compiled = false
@expanded_enum_traits = false
end
delegate :declare_attribute, to: :declarations
def attributes
@attributes ||= AttributeList.new.tap do |attribute_list|
attribute_lists = aggregate_from_traits_and_self(:attributes) { declarations.attributes }
attribute_lists.each do |attributes|
attribute_list.apply_attributes attributes
end
end
end
def to_create(&block)
if block
@to_create = block
else
aggregate_from_traits_and_self(:to_create) { @to_create }.last
end
end
def constructor
aggregate_from_traits_and_self(:constructor) { @constructor }.last
end
def callbacks
aggregate_from_traits_and_self(:callbacks) { @callbacks }
end
def compile(klass = nil)
unless @compiled
expand_enum_traits(klass) unless klass.nil?
declarations.attributes
self.klass ||= klass
defined_traits.each do |defined_trait|
defined_trait.klass ||= klass
base_traits.each { |bt| bt.define_trait defined_trait }
additional_traits.each { |at| at.define_trait defined_trait }
end
@compiled = true
ActiveSupport::Notifications.instrument "factory_bot.compile_factory", {
name: name,
attributes: declarations.attributes,
traits: defined_traits,
class: klass || self.klass
}
end
end
def overridable
declarations.overridable
self
end
def inherit_traits(new_traits)
@base_traits += new_traits
end
def append_traits(new_traits)
@additional_traits += new_traits
end
def add_callback(callback)
@callbacks << callback
end
def skip_create
@to_create = ->(instance) {}
end
def define_trait(trait)
@defined_traits.add(trait)
end
def defined_traits_names
@defined_traits.map(&:name)
end
def register_enum(enum)
@registered_enums << enum
end
def define_constructor(&block)
@constructor = block
end
def before(*names, &block)
callback(*names.map { |name| "before_#{name}" }, &block)
end
def after(*names, &block)
callback(*names.map { |name| "after_#{name}" }, &block)
end
def callback(*names, &block)
names.each do |name|
add_callback(Callback.new(name, block))
end
end
private
def base_traits
@base_traits.map { |name| trait_by_name(name) }
rescue KeyError => error
raise error_with_definition_name(error)
end
# detailed_message introduced in Ruby 3.2 for cleaner integration with
# did_you_mean. See https://bugs.ruby-lang.org/issues/18564
if KeyError.method_defined?(:detailed_message)
def error_with_definition_name(error)
message = error.message + " referenced within \"#{name}\" definition"
error.class.new(message, key: error.key, receiver: error.receiver)
.tap { |new_error| new_error.set_backtrace(error.backtrace) }
end
else
def error_with_definition_name(error)
message = error.message
message.insert(
message.index("\nDid you mean?") || message.length,
" referenced within \"#{name}\" definition"
)
error.class.new(message).tap do |new_error|
new_error.set_backtrace(error.backtrace)
end
end
end
def additional_traits
@additional_traits.map { |name| trait_by_name(name) }
end
def trait_by_name(name)
trait_for(name) || Internal.trait_by_name(name, klass)
end
def trait_for(name)
@defined_traits_by_name ||= defined_traits.each_with_object({}) { |t, memo| memo[t.name] ||= t }
@defined_traits_by_name[name.to_s]
end
def initialize_copy(source)
super
@attributes = nil
@compiled = false
@defined_traits_by_name = nil
end
def aggregate_from_traits_and_self(method_name, &block)
compile
[
base_traits.map(&method_name),
instance_exec(&block),
additional_traits.map(&method_name)
].flatten.compact
end
def expand_enum_traits(klass)
return if @expanded_enum_traits
if automatically_register_defined_enums?(klass)
automatically_register_defined_enums(klass)
end
registered_enums.each do |enum|
traits = enum.build_traits(klass)
traits.each { |trait| define_trait(trait) }
end
@expanded_enum_traits = true
end
def automatically_register_defined_enums(klass)
klass.defined_enums.each_key { |name| register_enum(Enum.new(name)) }
end
def automatically_register_defined_enums?(klass)
FactoryBot.automatically_define_enum_traits &&
klass.respond_to?(:defined_enums)
end
end
end
|