thoughtbot/factory_bot · error · FactoryBot::AssociationDefinitionError

Unexpected block passed to '#{name}' association in '#{@defi

Error message

Unexpected block passed to '#{name}' association in '#{@definition.name}' factory

What it means

AssociationDefinitionError raised by DefinitionProxy#association (definition_proxy.rb:155-160) when a block is passed to `association`. Associations are purely declarative; factory_bot cannot evaluate a block for them, and the guard fails fast during the FactoryBot.define block instead of silently ignoring the block.

Source

Thrown at lib/factory_bot/definition_proxy.rb:157

    #
    #   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.
    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

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

    def skip_create
      @definition.skip_create
    end

    def factory(name, options = {}, &block)

View on GitHub (pinned to 18ae8b581b)

Solutions

  1. Remove the block and pass overrides directly: `association :author, factory: :user, name: 'Jane'`.
  2. Move the customization into a trait on the target factory and pass the trait: `association :author, :with_name, factory: :user`.
  3. Use after(:build) / after(:create) callbacks on the parent factory to mutate the associated record.
  4. If you need dynamic construction, use the allowed block form `author { association(:author, name: 'Jane') }` — a Dynamic attribute whose block calls association.

Example fix

# before
FactoryBot.define do
  factory :post do
    association :author, factory: :user do |author|
      author.name = 'Jane'
    end
  end
end

# after
FactoryBot.define do
  factory :post do
    association :author, factory: :user, name: 'Jane'
  end
end
Defensive patterns

Strategy: validation

Validate before calling

# wrapper that refuses blocks on associations before factory_bot sees them
def association!(name, *options, &block)
  raise ArgumentError, "association #{name} takes no block" if block
  association(name, *options)
end

Type guard

->(blk) { blk.nil? }

Try / catch

begin
  FactoryBot.define { #...
 }
rescue FactoryBot::AssociationDefinitionError => e
  raise unless e.message.include?('Unexpected block passed')
  # move the block body into overrides, a trait, or an after(:build) callback
end

Prevention

When it happens

Trigger: Write `association :author, factory: :user do |a| ... end` (or the short form `author factory: :user do ... end`) inside a factory body. The definition block raises immediately with "Unexpected block passed to 'author' association in 'post' factory".

Common situations: Trying to customize the associated record inline, a habit carried over from `create(:user) { |u| ... }` result blocks; converting an attribute block into an association while leaving the block attached; refactoring shared setups where the block contained extra attribute assignments.

Related errors


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