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
- 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.
- 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.
- Create a child factory: `factory :comment_with_parent, parent: :comment do association :parent, factory: :comment end`.
- 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
- For self-referential models default the parent to nil and add a :with_parent trait using the block form.
- Never point `factory:` at the factory currently being defined in its own body.
- Run FactoryBot.lint(traits: true) in CI so self-references are caught before runtime.
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
- Association '#{name}' received an invalid factory argument.
- Association '#{name}' received an invalid attribute override
- Unexpected block passed to '#{name}' association in '#{@defi
- Self-referencing trait '#{@name}'
- undefined method '#{name}' in '#{@definition.name}' factory
AI-assisted analysis of thoughtbot/factory_bot@18ae8b581b (2026-08-21).
Data as JSON: /api/errors/ac84e0afc3ae67d9.
Report an issue: GitHub.