2017-10-20 15:20:28 -04:00
|
|
|
module FactoryBot
|
2010-06-10 14:58:47 -04:00
|
|
|
class DefinitionProxy
|
2020-06-05 15:15:18 -04:00
|
|
|
UNPROXIED_METHODS = %w[
|
2018-12-12 11:05:13 -05:00
|
|
|
__send__
|
|
|
|
__id__
|
|
|
|
nil?
|
|
|
|
send
|
|
|
|
object_id
|
|
|
|
extend
|
|
|
|
instance_eval
|
|
|
|
initialize
|
|
|
|
block_given?
|
|
|
|
raise
|
|
|
|
caller
|
|
|
|
method
|
2020-06-05 15:15:18 -04:00
|
|
|
].freeze
|
2011-07-05 18:20:39 -04:00
|
|
|
|
|
|
|
(instance_methods + private_instance_methods).each do |method|
|
2011-07-06 18:18:19 -04:00
|
|
|
undef_method(method) unless UNPROXIED_METHODS.include?(method.to_s)
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
2013-02-08 11:00:22 -05:00
|
|
|
delegate :before, :after, :callback, to: :@definition
|
|
|
|
|
2011-06-29 16:49:45 -04:00
|
|
|
attr_reader :child_factories
|
2011-08-12 14:49:21 -04:00
|
|
|
|
2011-10-28 17:01:27 -04:00
|
|
|
def initialize(definition, ignore = false)
|
2020-06-05 15:15:18 -04:00
|
|
|
@definition = definition
|
|
|
|
@ignore = ignore
|
2011-06-29 16:49:45 -04:00
|
|
|
@child_factories = []
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
2013-04-26 10:33:48 -04:00
|
|
|
def singleton_method_added(name)
|
|
|
|
message = "Defining methods in blocks (trait or factory) is not supported (#{name})"
|
2017-10-20 15:20:28 -04:00
|
|
|
raise FactoryBot::MethodDefinitionError, message
|
2013-04-26 10:33:48 -04:00
|
|
|
end
|
|
|
|
|
2018-09-14 15:02:33 -04:00
|
|
|
# Adds an attribute to the factory.
|
|
|
|
# The attribute value will be generated "lazily"
|
|
|
|
# by calling the block whenever an instance is generated.
|
|
|
|
# The block will not be called if the
|
2010-06-10 14:58:47 -04:00
|
|
|
# attribute is overridden for a specific instance.
|
|
|
|
#
|
|
|
|
# Arguments:
|
|
|
|
# * name: +Symbol+ or +String+
|
2011-02-07 17:48:00 -05:00
|
|
|
# The name of this attribute. This will be assigned using "name=" for
|
2010-06-10 14:58:47 -04:00
|
|
|
# generated instances.
|
2018-07-29 11:46:49 -04:00
|
|
|
def add_attribute(name, &block)
|
|
|
|
declaration = Declaration::Dynamic.new(name, @ignore, block)
|
2011-10-28 17:01:27 -04:00
|
|
|
@definition.declare_attribute(declaration)
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
2014-04-28 17:39:44 -04:00
|
|
|
def transient(&block)
|
2011-10-28 17:01:27 -04:00
|
|
|
proxy = DefinitionProxy.new(@definition, true)
|
2011-10-07 18:19:27 -04:00
|
|
|
proxy.instance_eval(&block)
|
|
|
|
end
|
|
|
|
|
2010-06-10 14:58:47 -04:00
|
|
|
# Calls add_attribute using the missing method name as the name of the
|
|
|
|
# attribute, so that:
|
|
|
|
#
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :user do
|
2018-07-29 11:46:49 -04:00
|
|
|
# name { 'Billy Idol' }
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# and:
|
|
|
|
#
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :user do
|
2018-07-29 11:46:49 -04:00
|
|
|
# add_attribute(:name) { 'Billy Idol' }
|
2010-10-01 18:50:01 -04:00
|
|
|
# end
|
|
|
|
#
|
2011-07-01 19:08:03 -04:00
|
|
|
# are equivalent.
|
2010-10-01 18:50:01 -04:00
|
|
|
#
|
2018-11-25 22:16:22 -05:00
|
|
|
# If no argument or block is given, factory_bot will first look for an
|
|
|
|
# association, then for a sequence, and finally for a trait with the same
|
|
|
|
# name. This means that given an "admin" trait, an "email" sequence, and an
|
|
|
|
# "account" factory:
|
2010-10-01 18:50:01 -04:00
|
|
|
#
|
2018-11-25 22:16:22 -05:00
|
|
|
# factory :user, traits: [:admin] do
|
|
|
|
# email { generate(:email) }
|
2010-10-01 18:50:01 -04:00
|
|
|
# association :account
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# and:
|
|
|
|
#
|
|
|
|
# factory :user do
|
2018-11-25 22:16:22 -05:00
|
|
|
# admin
|
2010-10-01 18:50:01 -04:00
|
|
|
# email
|
|
|
|
# account
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
2011-07-01 19:08:03 -04:00
|
|
|
# are equivalent.
|
2022-01-14 15:08:08 -05:00
|
|
|
def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing
|
2019-04-26 11:41:51 -04:00
|
|
|
association_options = args.first
|
|
|
|
|
|
|
|
if association_options.nil?
|
2018-07-29 11:46:49 -04:00
|
|
|
__declare_attribute__(name, block)
|
2019-04-26 11:41:51 -04:00
|
|
|
elsif __valid_association_options?(association_options)
|
|
|
|
association(name, association_options)
|
2010-07-06 21:15:16 -04:00
|
|
|
else
|
2019-04-23 22:45:55 -04:00
|
|
|
raise NoMethodError.new(<<~MSG)
|
|
|
|
undefined method '#{name}' in '#{@definition.name}' factory
|
2019-04-26 11:41:51 -04:00
|
|
|
Did you mean? '#{name} { #{association_options.inspect} }'
|
2019-04-23 22:45:55 -04:00
|
|
|
MSG
|
2010-07-06 21:15:16 -04:00
|
|
|
end
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Adds an attribute that will have unique values generated by a sequence with
|
|
|
|
# a specified format.
|
|
|
|
#
|
|
|
|
# The result of:
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :user do
|
|
|
|
# sequence(:email) { |n| "person#{n}@example.com" }
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Is equal to:
|
2010-10-01 18:50:01 -04:00
|
|
|
# sequence(:email) { |n| "person#{n}@example.com" }
|
2010-06-10 14:58:47 -04:00
|
|
|
#
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :user do
|
2017-10-20 15:20:28 -04:00
|
|
|
# email { FactoryBot.generate(:email) }
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Except that no globally available sequence will be defined.
|
2012-04-06 14:41:13 -04:00
|
|
|
def sequence(name, *args, &block)
|
2019-02-13 17:24:28 -05:00
|
|
|
sequence = Sequence.new(name, *args, &block)
|
|
|
|
FactoryBot::Internal.register_inline_sequence(sequence)
|
2013-01-11 12:05:17 -05:00
|
|
|
add_attribute(name) { increment_sequence(sequence) }
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Adds an attribute that builds an association. The associated instance will
|
|
|
|
# be built using the same build strategy as the parent instance.
|
|
|
|
#
|
|
|
|
# Example:
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :user do
|
|
|
|
# name 'Joey'
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
2010-10-01 18:50:01 -04:00
|
|
|
# factory :post do
|
2012-03-09 17:20:38 -05:00
|
|
|
# association :author, factory: :user
|
2010-06-10 14:58:47 -04:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Arguments:
|
|
|
|
# * name: +Symbol+
|
|
|
|
# The name of this attribute.
|
|
|
|
# * options: +Hash+
|
|
|
|
#
|
|
|
|
# Options:
|
|
|
|
# * factory: +Symbol+ or +String+
|
|
|
|
# The name of the factory to use when building the associated instance.
|
|
|
|
# If no name is given, the name of the attribute is assumed to be the
|
|
|
|
# name of the factory. For example, a "user" association will by
|
|
|
|
# default use the "user" factory.
|
2012-08-02 10:19:17 -04:00
|
|
|
def association(name, *options)
|
2018-10-28 19:12:34 -04:00
|
|
|
if block_given?
|
|
|
|
raise AssociationDefinitionError.new(
|
|
|
|
"Unexpected block passed to '#{name}' association "\
|
2020-06-05 15:15:18 -04:00
|
|
|
"in '#{@definition.name}' factory"
|
2018-10-28 19:12:34 -04:00
|
|
|
)
|
|
|
|
else
|
|
|
|
declaration = Declaration::Association.new(name, *options)
|
|
|
|
@definition.declare_attribute(declaration)
|
|
|
|
end
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
|
2010-11-12 16:21:16 -05:00
|
|
|
def to_create(&block)
|
2011-10-28 17:01:27 -04:00
|
|
|
@definition.to_create(&block)
|
2010-11-12 16:21:16 -05:00
|
|
|
end
|
2011-06-29 16:49:45 -04:00
|
|
|
|
2012-04-23 23:40:05 -04:00
|
|
|
def skip_create
|
2012-05-12 00:42:44 -04:00
|
|
|
@definition.skip_create
|
2012-04-23 23:40:05 -04:00
|
|
|
end
|
|
|
|
|
2011-06-29 16:49:45 -04:00
|
|
|
def factory(name, options = {}, &block)
|
|
|
|
@child_factories << [name, options, block]
|
|
|
|
end
|
2011-08-12 14:49:21 -04:00
|
|
|
|
2011-08-12 16:16:17 -04:00
|
|
|
def trait(name, &block)
|
2011-10-28 17:01:27 -04:00
|
|
|
@definition.define_trait(Trait.new(name, &block))
|
2011-08-09 20:29:02 -04:00
|
|
|
end
|
2012-01-20 13:04:48 -05:00
|
|
|
|
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
|
|
|
# Creates traits for enumerable values.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# factory :task do
|
|
|
|
# traits_for_enum :status, [:started, :finished]
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Equivalent to:
|
|
|
|
# factory :task do
|
|
|
|
# trait :started do
|
|
|
|
# status { :started }
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# trait :finished do
|
|
|
|
# status { :finished }
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# factory :task do
|
|
|
|
# traits_for_enum :status, {started: 1, finished: 2}
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# class Task
|
|
|
|
# def statuses
|
|
|
|
# {started: 1, finished: 2}
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# factory :task do
|
|
|
|
# traits_for_enum :status
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Both equivalent to:
|
|
|
|
# factory :task do
|
|
|
|
# trait :started do
|
|
|
|
# status { 1 }
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# trait :finished do
|
|
|
|
# status { 2 }
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# Arguments:
|
|
|
|
# attribute_name: +Symbol+ or +String+
|
|
|
|
# the name of the attribute these traits will set the value of
|
|
|
|
# values: +Array+, +Hash+, or other +Enumerable+
|
|
|
|
# An array of trait names, or a mapping of trait names to values for
|
|
|
|
# those traits. When this argument is not provided, factory_bot will
|
|
|
|
# attempt to get the values by calling the pluralized `attribute_name`
|
|
|
|
# class method.
|
|
|
|
def traits_for_enum(attribute_name, values = nil)
|
|
|
|
@definition.register_enum(Enum.new(attribute_name, values))
|
|
|
|
end
|
|
|
|
|
2012-01-20 13:04:48 -05:00
|
|
|
def initialize_with(&block)
|
|
|
|
@definition.define_constructor(&block)
|
|
|
|
end
|
2018-08-01 17:45:38 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-07-29 11:46:49 -04:00
|
|
|
def __declare_attribute__(name, block)
|
|
|
|
if block.nil?
|
|
|
|
declaration = Declaration::Implicit.new(name, @definition, @ignore)
|
|
|
|
@definition.declare_attribute(declaration)
|
|
|
|
else
|
|
|
|
add_attribute(name, &block)
|
2018-08-01 17:45:38 -04:00
|
|
|
end
|
|
|
|
end
|
2019-04-26 11:41:51 -04:00
|
|
|
|
|
|
|
def __valid_association_options?(options)
|
|
|
|
options.respond_to?(:has_key?) && options.has_key?(:factory)
|
|
|
|
end
|
2010-06-10 14:58:47 -04:00
|
|
|
end
|
|
|
|
end
|