Class: FactoryBot::DefinitionProxy

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

Constant Summary collapse

UNPROXIED_METHODS =
%w[
  __send__
  __id__
  nil?
  send
  object_id
  extend
  instance_eval
  initialize
  block_given?
  raise
  caller
  method
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, ignore = false) ⇒ DefinitionProxy

Returns a new instance of DefinitionProxy.


26
27
28
29
30
# File 'lib/factory_bot/definition_proxy.rb', line 26

def initialize(definition, ignore = false)
  @definition = definition
  @ignore = ignore
  @child_factories = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Calls add_attribute using the missing method name as the name of the attribute, so that:

factory :user do
  name { 'Billy Idol' }
end

and:

factory :user do
  add_attribute(:name) { 'Billy Idol' }
end

are equivalent.

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:

factory :user, traits: [:admin] do
  email { generate(:email) }
  association :account
end

and:

factory :user do
  admin
  email
  
end

are equivalent.


91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/factory_bot/definition_proxy.rb', line 91

def method_missing(name, *args, &block) # rubocop:disable Style/MissingRespondToMissing, Style/MethodMissingSuper
  association_options = args.first

  if association_options.nil?
    __declare_attribute__(name, block)
  elsif __valid_association_options?(association_options)
    association(name, association_options)
  else
    raise NoMethodError.new(<<~MSG)
      undefined method '#{name}' in '#{@definition.name}' factory
      Did you mean? '#{name} { #{association_options.inspect} }'
    MSG
  end
end

Instance Attribute Details

#child_factoriesObject (readonly)

Returns the value of attribute child_factories


24
25
26
# File 'lib/factory_bot/definition_proxy.rb', line 24

def child_factories
  @child_factories
end

Instance Method Details

#add_attribute(name, &block) ⇒ Object

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 attribute is overridden for a specific instance.

Arguments:

  • name: Symbol or String The name of this attribute. This will be assigned using “name=” for generated instances.


47
48
49
50
# File 'lib/factory_bot/definition_proxy.rb', line 47

def add_attribute(name, &block)
  declaration = Declaration::Dynamic.new(name, @ignore, block)
  @definition.declare_attribute(declaration)
end

#association(name, *options) ⇒ Object

Adds an attribute that builds an association. The associated instance will be built using the same build strategy as the parent instance.

Example:

factory :user do
  name 'Joey'
end

factory :post do
  association :author, factory: :user
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.
    

151
152
153
154
155
156
157
158
159
160
161
# File 'lib/factory_bot/definition_proxy.rb', line 151

def association(name, *options)
  if block_given?
    raise AssociationDefinitionError.new(
      "Unexpected block passed to '#{name}' association "\
      "in '#{@definition.name}' factory"
    )
  else
    declaration = Declaration::Association.new(name, *options)
    @definition.declare_attribute(declaration)
  end
end

#factory(name, options = {}, &block) ⇒ Object


171
172
173
# File 'lib/factory_bot/definition_proxy.rb', line 171

def factory(name, options = {}, &block)
  @child_factories << [name, options, block]
end

#initialize_with(&block) ⇒ Object


237
238
239
# File 'lib/factory_bot/definition_proxy.rb', line 237

def initialize_with(&block)
  @definition.define_constructor(&block)
end

#sequence(name, *args, &block) ⇒ Object

Adds an attribute that will have unique values generated by a sequence with a specified format.

The result of:

factory :user do
  sequence(:email) { |n| "person#{n}@example.com" }
end

Is equal to:

sequence(:email) { |n| "person#{n}@example.com" }

factory :user do
  email { FactoryBot.generate(:email) }
end

Except that no globally available sequence will be defined.


122
123
124
125
126
# File 'lib/factory_bot/definition_proxy.rb', line 122

def sequence(name, *args, &block)
  sequence = Sequence.new(name, *args, &block)
  FactoryBot::Internal.register_inline_sequence(sequence)
  add_attribute(name) { increment_sequence(sequence) }
end

#singleton_method_added(name) ⇒ Object


32
33
34
35
# File 'lib/factory_bot/definition_proxy.rb', line 32

def singleton_method_added(name)
  message = "Defining methods in blocks (trait or factory) is not supported (#{name})"
  raise FactoryBot::MethodDefinitionError, message
end

#skip_createObject


167
168
169
# File 'lib/factory_bot/definition_proxy.rb', line 167

def skip_create
  @definition.skip_create
end

#to_create(&block) ⇒ Object


163
164
165
# File 'lib/factory_bot/definition_proxy.rb', line 163

def to_create(&block)
  @definition.to_create(&block)
end

#trait(name, &block) ⇒ Object


175
176
177
# File 'lib/factory_bot/definition_proxy.rb', line 175

def trait(name, &block)
  @definition.define_trait(Trait.new(name, &block))
end

#traits_for_enum(attribute_name, values = nil) ⇒ Object

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.

233
234
235
# File 'lib/factory_bot/definition_proxy.rb', line 233

def traits_for_enum(attribute_name, values = nil)
  @definition.register_enum(Enum.new(attribute_name, values))
end

#transient(&block) ⇒ Object


52
53
54
55
# File 'lib/factory_bot/definition_proxy.rb', line 52

def transient(&block)
  proxy = DefinitionProxy.new(@definition, true)
  proxy.instance_eval(&block)
end