thoughtbot--factory_bot/lib/factory_bot/factory.rb

164 lines
4.0 KiB
Ruby

require "active_support/core_ext/hash/keys"
require "active_support/inflector"
module FactoryBot
# @api private
class Factory
attr_reader :name, :definition
def initialize(name, options = {})
assert_valid_options(options)
@name = name.respond_to?(:to_sym) ? name.to_sym : name.to_s.underscore.to_sym
@parent = options[:parent]
@aliases = options[:aliases] || []
@class_name = options[:class]
@definition = Definition.new(@name, options[:traits] || [])
@compiled = false
end
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
:defined_traits, :inherit_traits, :append_traits, to: :@definition
def build_class
@build_class ||= if class_name.is_a? Class
class_name
else
class_name.to_s.camelize.constantize
end
end
def run(build_strategy, overrides, &block)
block ||= ->(result) { result }
compile
strategy = StrategyCalculator.new(build_strategy).strategy.new
evaluator = evaluator_class.new(strategy, overrides.symbolize_keys)
attribute_assigner = AttributeAssigner.new(evaluator, build_class, &compiled_constructor)
evaluation =
Evaluation.new(evaluator, attribute_assigner, compiled_to_create)
evaluation.add_observer(CallbacksObserver.new(callbacks, evaluator))
strategy.result(evaluation).tap(&block)
end
def human_names
names.map { |name| name.to_s.humanize.downcase }
end
def associations
evaluator_class.attribute_list.associations
end
# Names for this factory, including aliases.
#
# Example:
#
# factory :user, aliases: [:author] do
# # ...
# end
#
# FactoryBot.create(:author).class
# # => User
#
# Because an attribute defined without a value or block will build an
# association with the same name, this allows associations to be defined
# without factories, such as:
#
# factory :user, aliases: [:author] do
# # ...
# end
#
# factory :post do
# author
# end
#
# FactoryBot.create(:post).author.class
# # => User
def names
[name] + @aliases
end
def compile
unless @compiled
parent.compile
parent.defined_traits.each { |trait| define_trait(trait) }
@definition.compile
build_hierarchy
@compiled = true
end
end
def with_traits(traits)
clone.tap do |factory_with_traits|
factory_with_traits.append_traits traits
end
end
protected
def class_name
@class_name || parent.class_name || name
end
def evaluator_class
@evaluator_class ||= EvaluatorClassDefiner.new(attributes, parent.evaluator_class).evaluator_class
end
def attributes
compile
AttributeList.new(@name).tap do |list|
list.apply_attributes definition.attributes
end
end
def hierarchy_class
@hierarchy_class ||= Class.new(parent.hierarchy_class)
end
def hierarchy_instance
@hierarchy_instance ||= hierarchy_class.new
end
def build_hierarchy
hierarchy_class.build_from_definition definition
end
def callbacks
hierarchy_instance.callbacks
end
def compiled_to_create
hierarchy_instance.to_create
end
def compiled_constructor
hierarchy_instance.constructor
end
private
def assert_valid_options(options)
options.assert_valid_keys(:class, :parent, :aliases, :traits)
end
def parent
if @parent
FactoryBot.factory_by_name(@parent)
else
NullFactory.new
end
end
def initialize_copy(source)
super
@definition = @definition.clone
@evaluator_class = nil
@hierarchy_class = nil
@hierarchy_instance = nil
@compiled = false
end
end
end