thoughtbot/factory_bot · error · FactoryBot::MethodDefinitionError

Defining methods in blocks (trait or factory) is not support

Error message

Defining methods in blocks (trait or factory) is not supported (#{name})

What it means

MethodDefinitionError raised via DefinitionProxy#singleton_method_added (definition_proxy.rb:32-35). Factory, trait, and transient bodies are instance_eval'd against a DefinitionProxy from which almost all methods have been undefined; writing `def something` inside the block defines a singleton method on the proxy, which fires the hook and raises immediately. DSL blocks only accept attribute declarations, so helper methods are rejected.

Source

Thrown at lib/factory_bot/definition_proxy.rb:34

    ].freeze

    (instance_methods + private_instance_methods).each do |method|
      undef_method(method) unless UNPROXIED_METHODS.include?(method.to_s)
    end

    delegate :before, :after, :callback, to: :@definition

    attr_reader :child_factories

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

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

    # 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.
    def add_attribute(name, &block)
      declaration = Declaration::Dynamic.new(name, @ignore, block)
      @definition.declare_attribute(declaration)
    end

    def transient(&block)

View on GitHub (pinned to 18ae8b581b)

Solutions

  1. Inline the logic into the attribute block, or compute it in a transient attribute that other attributes reference.
  2. Put the helper on the model or in a plain module and call it from the blocks.
  3. For helpers needed inside dynamic blocks, include a module into FactoryBot::SyntaxRunner via an initializer (`FactoryBot::SyntaxRunner.include MyHelpers`).
  4. Use `initialize_with { ... }` to route construction through a builder that encapsulates the logic.

Example fix

# before
FactoryBot.define do
  factory :user do
    def email_domain; 'example.com'; end # raises MethodDefinitionError
    email { "user@#{email_domain}" }
  end
end

# after
FactoryBot.define do
  factory :user do
    email { 'user@example.com' } # inline it, or use a model/SyntaxRunner helper
  end
end
Defensive patterns

Strategy: validation

Try / catch

begin
  require 'spec/factories'
rescue FactoryBot::MethodDefinitionError => e
  # e.message names the method; move the def to the model, a module, or FactoryBot::SyntaxRunner
end

Prevention

When it happens

Trigger: Inside `factory :user do ... end`, `trait :x do ... end`, or `transient do ... end`, write `def email_domain; 'example.com'; end`. The hook raises at definition (load) time: "Defining methods in blocks (trait or factory) is not supported (email_domain)".

Common situations: Trying to DRY logic shared by several attribute blocks; developers used to block DSLs (Rake, RSpec) where `def` inside a block works; porting object-mother or fixture code that contained helper methods.

Related errors


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