thoughtbot/factory_bot · error · FactoryBot::AttributeDefinitionError

Attribute already defined: #{attribute.name}

Error message

Attribute already defined: #{attribute.name}

What it means

AttributeDefinitionError raised by AttributeList#ensure_attribute_not_defined! (attribute_list.rb:49-53) when two declarations in the same factory body, trait body, or transient block compile to attributes with the same name. Declarations are checked when the attribute list is compiled (first build/create), so the error surfaces lazily. Overriding an attribute inherited from a parent factory is legal — inherited attributes merge via apply_attributes, which bypasses this check.

Source

Thrown at lib/factory_bot/attribute_list.rb:51

    def non_transient
      AttributeList.new(@name, reject(&:ignored))
    end

    def apply_attributes(attributes_to_apply)
      attributes_to_apply.each { |attribute| add_attribute(attribute) }
    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. Locate both declarations for the named attribute in that factory/trait/transient body and delete or rename one.
  2. If one is a sequence and one a block, keep the sequence and remove the explicit attribute (or vice versa).
  3. Remember same-body duplicates are the problem — redefining an inherited attribute in a child factory is allowed and not the cause.
  4. Run FactoryBot.lint(traits: true) so duplicates surface in CI with the factory name.

Example fix

# before
FactoryBot.define do
  factory :user do
    email { 'a@example.com' }
    sequence :email { |n| "user#{n}@example.com" } # duplicate :email
  end
end

# after
FactoryBot.define do
  factory :user do
    sequence :email { |n| "user#{n}@example.com" }
  end
end
Defensive patterns

Strategy: validation

Validate before calling

# after defining, verify no duplicate attribute names before first use
names = FactoryBot::Internal.factory_by_name(:user).definition.declarations.map(&:name)
dups = names.tally.select { |_, c| c > 1 }.keys
raise "duplicate attributes in :user factory: #{dups}" unless dups.empty?

Type guard

->(names) { names.size == names.uniq.size }

Try / catch

begin
  FactoryBot.build(:user)
rescue FactoryBot::AttributeDefinitionError => e
  # e.message names the duplicated attribute; remove or rename one of its declarations
end

Prevention

When it happens

Trigger: `factory :user do name { 'A' }; name { 'B' } end`; combining `sequence :email { |n| ... }` with an explicit `email { 'a@b.c' }` in the same body; declaring `email` in both the body and the same transient block (transient shares the definition's declaration list). Raises 'Attribute already defined: name' on first use of the factory.

Common situations: Copy-paste of attribute blocks that leaves two similar lines; renaming an attribute without removing the old declaration; adding a sequence for an attribute still set by a block; merging two factories into one body during refactoring.

Related errors


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