thoughtbot/factory_bot · error · FactoryBot::AssociationDefinitionError

Self-referencing association '#{attribute.name}' in '#{attri

Error message

Self-referencing association '#{attribute.name}' in '#{attribute.factory}'

What it means

AssociationDefinitionError raised by AttributeList#ensure_attribute_not_self_referencing! (attribute_list.rb:55-60) when an association declared directly in a factory's body targets that same factory (attribute.factory == the list's name, which is the factory's name). Examples: `association :parent, factory: :comment` inside `factory :comment`, or implicit `association :author` inside `factory :author`. The guard prevents infinite recursion when the declaration is evaluated.

Source

Thrown at lib/factory_bot/attribute_list.rb:58

    end

    private

    def add_attribute(attribute)
      @attributes << attribute
      attribute
    end

    def ensure_attribute_not_defined!(attribute)
      if attribute_defined?(attribute.name)
        raise AttributeDefinitionError, "Attribute already defined: #{attribute.name}"
      end
    end

    def ensure_attribute_not_self_referencing!(attribute)
      if attribute.respond_to?(:factory) && attribute.factory == @name
        message = "Self-referencing association '#{attribute.name}' in '#{attribute.factory}'"
        raise AssociationDefinitionError, message
      end
    end

    def attribute_defined?(attribute_name)
      @attributes.any? do |attribute|
        attribute.name == attribute_name
      end
    end
  end
end

View on GitHub (pinned to 18ae8b581b)

Solutions

  1. Use the dynamic block form, evaluated at build time and not subject to the declaration check: `parent { nil }` or `parent { association(:comment) }`, then override per test.
  2. Move the association into a trait (`trait :with_parent do association :parent, factory: :comment end`) — a trait's attribute list is named after the trait, so the guard does not fire.
  3. Create a child factory: `factory :comment_with_parent, parent: :comment do association :parent, factory: :comment end`.
  4. Or set the parent explicitly at call time: `create(:comment, parent: create(:comment))`.

Example fix

# before
FactoryBot.define do
  factory :comment do
    body { 'hi' }
    association :parent, factory: :comment # self-referencing -> raises
  end
end

# after
FactoryBot.define do
  factory :comment do
    body { 'hi' }
    parent { nil }

    trait :with_parent do
      parent { association(:comment) } # block form evaluates at build time
    end
  end
end
Defensive patterns

Strategy: validation

Validate before calling

# refuse same-factory associations up front
def safe_association(factory_name, name, *options)
  opts = options.extract_options!
  target = opts.fetch(:factory, name)
  if target.to_s == factory_name.to_s
    raise FactoryBot::AssociationDefinitionError, "#{name} would self-reference :#{factory_name}"
  end
  association(name, *options)
end

Type guard

->(target, own_name) { target.to_s != own_name.to_s }

Try / catch

begin
  FactoryBot.build(:comment)
rescue FactoryBot::AssociationDefinitionError => e
  raise unless e.message.include?('Self-referencing association')
  # switch to `parent { association(:comment) }` (block form) or a :with_parent trait
end

Prevention

When it happens

Trigger: Modeling tree structures: `FactoryBot.define { factory :comment do association :parent, factory: :comment end }`, or `factory :author do association :author end` where the attribute name implicitly selects the same factory. Raises when declarations are compiled, i.e. on the first build/create/lint.

Common situations: Self-referential ActiveRecord models (comments, categories, org charts, referral trees) with `belongs_to :parent, class_name: 'Comment'`; porting fixtures that used nil parents; the first attempt at a factory for a recursive model almost always hits this guard.

Related errors


AI-assisted analysis of thoughtbot/factory_bot@18ae8b581b (2026-08-21). Data as JSON: /api/errors/ac84e0afc3ae67d9. Report an issue: GitHub.