thoughtbot/factory_bot · error · FactoryBot::MethodDefinitionError

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

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.

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.

Appendix: 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)