2018-10-07 21:45:51 -04:00
|
|
|
require "active_support/core_ext/hash/keys"
|
|
|
|
require "active_support/inflector"
|
2011-09-23 15:00:00 -04:00
|
|
|
|
2017-10-20 15:20:28 -04:00
|
|
|
module FactoryBot
|
2012-05-05 02:31:31 -04:00
|
|
|
# @api private
|
2010-06-24 09:45:57 -04:00
|
|
|
class Factory
|
2012-05-05 02:31:31 -04:00
|
|
|
attr_reader :name, :definition
|
2011-08-12 10:35:41 -04:00
|
|
|
|
2012-05-05 02:31:31 -04:00
|
|
|
def initialize(name, options = {})
|
2010-06-24 09:45:57 -04:00
|
|
|
assert_valid_options(options)
|
2020-06-05 15:15:18 -04:00
|
|
|
@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
|
2011-09-02 12:05:00 -04:00
|
|
|
end
|
|
|
|
|
2012-05-11 11:43:08 -04:00
|
|
|
delegate :add_callback, :declare_attribute, :to_create, :define_trait, :constructor,
|
2020-06-05 15:15:18 -04:00
|
|
|
:defined_traits, :inherit_traits, :append_traits, to: :@definition
|
2011-10-14 22:34:51 -04:00
|
|
|
|
2012-05-05 02:31:31 -04:00
|
|
|
def build_class
|
2011-12-14 17:10:32 -05:00
|
|
|
@build_class ||= if class_name.is_a? Class
|
2020-06-05 15:15:18 -04:00
|
|
|
class_name
|
|
|
|
else
|
|
|
|
class_name.to_s.camelize.constantize
|
|
|
|
end
|
2011-10-07 16:15:15 -04:00
|
|
|
end
|
|
|
|
|
2012-05-05 02:31:31 -04:00
|
|
|
def run(build_strategy, overrides, &block)
|
2012-04-20 16:59:39 -04:00
|
|
|
block ||= ->(result) { result }
|
2011-12-04 01:34:50 -05:00
|
|
|
compile
|
2011-11-20 21:42:59 -05:00
|
|
|
|
2012-05-04 13:36:46 -04:00
|
|
|
strategy = StrategyCalculator.new(build_strategy).strategy.new
|
2011-12-16 11:15:13 -05:00
|
|
|
|
2012-07-24 18:50:11 -04:00
|
|
|
evaluator = evaluator_class.new(strategy, overrides.symbolize_keys)
|
2012-05-12 18:53:17 -04:00
|
|
|
attribute_assigner = AttributeAssigner.new(evaluator, build_class, &compiled_constructor)
|
2011-12-16 11:15:13 -05:00
|
|
|
|
2017-09-28 08:17:17 -04:00
|
|
|
evaluation =
|
|
|
|
Evaluation.new(evaluator, attribute_assigner, compiled_to_create)
|
2012-04-20 16:41:27 -04:00
|
|
|
evaluation.add_observer(CallbacksObserver.new(callbacks, evaluator))
|
2012-04-13 14:20:19 -04:00
|
|
|
|
|
|
|
strategy.result(evaluation).tap(&block)
|
2010-06-24 09:45:57 -04:00
|
|
|
end
|
2009-09-15 15:47:47 -04:00
|
|
|
|
2011-06-30 18:27:25 -04:00
|
|
|
def human_names
|
2013-12-14 22:33:15 -05:00
|
|
|
names.map { |name| name.to_s.humanize.downcase }
|
2009-09-15 15:47:47 -04:00
|
|
|
end
|
|
|
|
|
2010-06-24 09:45:57 -04:00
|
|
|
def associations
|
2012-02-08 08:14:34 -05:00
|
|
|
evaluator_class.attribute_list.associations
|
2010-06-24 09:45:57 -04:00
|
|
|
end
|
2009-09-15 16:56:20 -04:00
|
|
|
|
2011-01-25 17:55:40 -05:00
|
|
|
# Names for this factory, including aliases.
|
2010-11-11 17:34:01 -05:00
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
2012-03-09 17:20:38 -05:00
|
|
|
# factory :user, aliases: [:author] do
|
2010-11-11 17:34:01 -05:00
|
|
|
# # ...
|
|
|
|
# end
|
|
|
|
#
|
2017-10-20 15:20:28 -04:00
|
|
|
# FactoryBot.create(:author).class
|
2010-11-11 17:34:01 -05:00
|
|
|
# # => 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:
|
|
|
|
#
|
2012-03-09 17:20:38 -05:00
|
|
|
# factory :user, aliases: [:author] do
|
2010-11-11 17:34:01 -05:00
|
|
|
# # ...
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# factory :post do
|
|
|
|
# author
|
|
|
|
# end
|
|
|
|
#
|
2017-10-20 15:20:28 -04:00
|
|
|
# FactoryBot.create(:post).author.class
|
2010-11-11 17:34:01 -05:00
|
|
|
# # => User
|
2011-01-25 17:55:40 -05:00
|
|
|
def names
|
2011-09-23 16:33:39 -04:00
|
|
|
[name] + @aliases
|
2010-11-11 17:34:01 -05:00
|
|
|
end
|
|
|
|
|
2011-10-28 10:59:49 -04:00
|
|
|
def compile
|
2012-01-08 00:57:40 -05:00
|
|
|
unless @compiled
|
|
|
|
parent.compile
|
2013-12-14 22:33:15 -05:00
|
|
|
parent.defined_traits.each { |trait| define_trait(trait) }
|
Add functionality for enum traits (#1380)
## Enum traits
Given a Rails model with an enum attribute:
```rb
class Task < ActiveRecord::Base
enum status: {queued: 0, started: 1, finished: 2}
end
```
It is common for people to define traits for each possible value of the enum:
```rb
FactoryBot.define do
factory :task do
trait :queued do
status { :queued }
end
trait :started do
status { :started }
end
trait :finished do
status { :finished }
end
end
end
```
With this commit, those trait definitions are no longer necessary—they are defined automatically by factory_bot.
If automatically defining traits for enum attributes on every factory is not desired, it is possible to disable the feature by setting `FactoryBot.automatically_define_enum_traits = false` (see commit: [Allow opting out of automatically defining traits](https://github.com/thoughtbot/factory_bot/pull/1380/commits/5a20017351b08ce2ec9918d799e187e9eaa3ec32)).
In that case, it is still possible to explicitly define traits for an enum attribute in a particular factory:
```rb
FactoryBot.automatically_define_enum_traits = false
FactoryBot.define do
factory :task do
traits_for_enum(:status)
end
end
```
It is also possible to use this feature for other enumerable values, not specifically tied to ActiveRecord enum attributes:
```rb
class Task
attr_accessor :status
end
FactoryBot.define do
factory :task do
traits_for_enum(:status, ["queued", "started", "finished"])
end
end
```
The second argument here can be an enumerable object, including a Hash or Array.
Closes #1049
Co-authored-by: Lance Johnson <lancejjohnson@gmail.com>
Co-authored-by: PoTa <pota@mosfet.hu>
Co-authored-by: Frida Casas <fridacasas.fc@gmail.com>
Co-authored-by: Daniel Colson <danieljamescolson@gmail.com>
2020-05-01 17:50:51 -04:00
|
|
|
@definition.compile(build_class)
|
2012-07-11 21:32:03 -04:00
|
|
|
build_hierarchy
|
2012-01-08 00:57:40 -05:00
|
|
|
@compiled = true
|
|
|
|
end
|
2011-10-07 15:00:38 -04:00
|
|
|
end
|
|
|
|
|
Traits can be added to factories when the factory creates an instance
This allows for traits to be used with normal factories without having
to name every single factory that uses one (or many) traits.
So, instead of creating male_admin and female_admin factories:
FactoryGirl.define do
factory :user do
trait(:admin) { admin true }
trait(:male) { gender "Male" }
trait(:female) { gender "Female" }
factory :male_admin, :traits => [:male, :admin]
factory :female_admin, :traits => [:admin, :female]
end
end
FactoryGirl.create(:male_admin)
FactoryGirl.create(:female_admin)
You could just create a user with those traits assigned:
FactoryGirl.create(:user, :admin, :male)
FactoryGirl.create(:user, :admin, :female)
This can be combined with attribute overrides as expected.
FactoryGirl.create(:user, :admin, :male, :name => "John Doe")
FactoryGirl.create(:user, :admin, :female, :name => "Jane Doe")
2011-11-18 09:25:49 -05:00
|
|
|
def with_traits(traits)
|
2018-10-07 18:02:54 -04:00
|
|
|
clone.tap do |factory_with_traits|
|
2012-05-06 16:56:37 -04:00
|
|
|
factory_with_traits.append_traits traits
|
Traits can be added to factories when the factory creates an instance
This allows for traits to be used with normal factories without having
to name every single factory that uses one (or many) traits.
So, instead of creating male_admin and female_admin factories:
FactoryGirl.define do
factory :user do
trait(:admin) { admin true }
trait(:male) { gender "Male" }
trait(:female) { gender "Female" }
factory :male_admin, :traits => [:male, :admin]
factory :female_admin, :traits => [:admin, :female]
end
end
FactoryGirl.create(:male_admin)
FactoryGirl.create(:female_admin)
You could just create a user with those traits assigned:
FactoryGirl.create(:user, :admin, :male)
FactoryGirl.create(:user, :admin, :female)
This can be combined with attribute overrides as expected.
FactoryGirl.create(:user, :admin, :male, :name => "John Doe")
FactoryGirl.create(:user, :admin, :female, :name => "Jane Doe")
2011-11-18 09:25:49 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-10-07 15:00:38 -04:00
|
|
|
protected
|
|
|
|
|
2012-05-05 02:31:31 -04:00
|
|
|
def class_name
|
2011-10-28 21:23:06 -04:00
|
|
|
@class_name || parent.class_name || name
|
2011-10-07 15:00:38 -04:00
|
|
|
end
|
|
|
|
|
2012-01-03 09:31:40 -05:00
|
|
|
def evaluator_class
|
2012-04-13 14:20:19 -04:00
|
|
|
@evaluator_class ||= EvaluatorClassDefiner.new(attributes, parent.evaluator_class).evaluator_class
|
2012-01-03 09:31:40 -05:00
|
|
|
end
|
|
|
|
|
2011-10-07 16:43:36 -04:00
|
|
|
def attributes
|
2011-10-28 10:59:49 -04:00
|
|
|
compile
|
2011-10-28 17:01:27 -04:00
|
|
|
AttributeList.new(@name).tap do |list|
|
2012-06-12 23:11:55 -04:00
|
|
|
list.apply_attributes definition.attributes
|
2011-10-07 16:43:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-07-11 21:32:03 -04:00
|
|
|
def hierarchy_class
|
|
|
|
@hierarchy_class ||= Class.new(parent.hierarchy_class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def hierarchy_instance
|
|
|
|
@hierarchy_instance ||= hierarchy_class.new
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_hierarchy
|
2012-09-14 08:22:28 -04:00
|
|
|
hierarchy_class.build_from_definition definition
|
2012-07-11 21:32:03 -04:00
|
|
|
end
|
|
|
|
|
2011-10-07 16:06:28 -04:00
|
|
|
def callbacks
|
2012-07-11 21:32:03 -04:00
|
|
|
hierarchy_instance.callbacks
|
2011-10-07 16:06:28 -04:00
|
|
|
end
|
|
|
|
|
2012-05-11 11:43:08 -04:00
|
|
|
def compiled_to_create
|
2012-07-11 21:32:03 -04:00
|
|
|
hierarchy_instance.to_create
|
2012-05-11 11:43:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def compiled_constructor
|
2012-07-11 21:32:03 -04:00
|
|
|
hierarchy_instance.constructor
|
2012-01-20 13:04:48 -05:00
|
|
|
end
|
|
|
|
|
2011-10-28 10:59:49 -04:00
|
|
|
private
|
2011-09-23 13:14:02 -04:00
|
|
|
|
2010-06-24 09:45:57 -04:00
|
|
|
def assert_valid_options(options)
|
2012-03-09 15:24:40 -05:00
|
|
|
options.assert_valid_keys(:class, :parent, :aliases, :traits)
|
2008-12-11 15:54:33 -05:00
|
|
|
end
|
2010-06-07 15:51:18 -04:00
|
|
|
|
2011-10-07 15:00:38 -04:00
|
|
|
def parent
|
2011-10-28 21:23:06 -04:00
|
|
|
if @parent
|
2019-04-26 15:48:37 -04:00
|
|
|
FactoryBot::Internal.factory_by_name(@parent)
|
2011-10-28 21:23:06 -04:00
|
|
|
else
|
|
|
|
NullFactory.new
|
|
|
|
end
|
2011-10-07 15:00:38 -04:00
|
|
|
end
|
2011-10-20 11:29:57 -04:00
|
|
|
|
Traits can be added to factories when the factory creates an instance
This allows for traits to be used with normal factories without having
to name every single factory that uses one (or many) traits.
So, instead of creating male_admin and female_admin factories:
FactoryGirl.define do
factory :user do
trait(:admin) { admin true }
trait(:male) { gender "Male" }
trait(:female) { gender "Female" }
factory :male_admin, :traits => [:male, :admin]
factory :female_admin, :traits => [:admin, :female]
end
end
FactoryGirl.create(:male_admin)
FactoryGirl.create(:female_admin)
You could just create a user with those traits assigned:
FactoryGirl.create(:user, :admin, :male)
FactoryGirl.create(:user, :admin, :female)
This can be combined with attribute overrides as expected.
FactoryGirl.create(:user, :admin, :male, :name => "John Doe")
FactoryGirl.create(:user, :admin, :female, :name => "Jane Doe")
2011-11-18 09:25:49 -05:00
|
|
|
def initialize_copy(source)
|
|
|
|
super
|
|
|
|
@definition = @definition.clone
|
2012-01-16 12:29:43 -05:00
|
|
|
@evaluator_class = nil
|
2012-07-11 21:32:03 -04:00
|
|
|
@hierarchy_class = nil
|
|
|
|
@hierarchy_instance = nil
|
|
|
|
@compiled = false
|
Traits can be added to factories when the factory creates an instance
This allows for traits to be used with normal factories without having
to name every single factory that uses one (or many) traits.
So, instead of creating male_admin and female_admin factories:
FactoryGirl.define do
factory :user do
trait(:admin) { admin true }
trait(:male) { gender "Male" }
trait(:female) { gender "Female" }
factory :male_admin, :traits => [:male, :admin]
factory :female_admin, :traits => [:admin, :female]
end
end
FactoryGirl.create(:male_admin)
FactoryGirl.create(:female_admin)
You could just create a user with those traits assigned:
FactoryGirl.create(:user, :admin, :male)
FactoryGirl.create(:user, :admin, :female)
This can be combined with attribute overrides as expected.
FactoryGirl.create(:user, :admin, :male, :name => "John Doe")
FactoryGirl.create(:user, :admin, :female, :name => "Jane Doe")
2011-11-18 09:25:49 -05:00
|
|
|
end
|
2010-06-24 09:45:57 -04:00
|
|
|
end
|
2008-05-28 18:20:25 -04:00
|
|
|
end
|