lostisland/faraday · error · ArgumentError

#memoized must be called with a block

Error message

#memoized must be called with a block

What it means

Faraday::Options subclasses (request options, ssl options, proxy options and user-defined ones) get their attribute DSL from two class-level helpers: options(mapping) for plain attributes and memoized(key, &block), which registers a block that computes the attribute's default and uses class_eval to define a reader delegating to self[:key]. The block is mandatory — memoized raises ArgumentError immediately when called without one.

Source

Thrown at lib/faraday/options.rb:172

    # Internal
    def self.options(mapping)
      attribute_options.update(mapping)
    end

    # Internal
    def self.options_for(key)
      attribute_options[key]
    end

    # Internal
    def self.attribute_options
      @attribute_options ||= {}
    end

    def self.memoized(key, &block)
      unless block
        raise ArgumentError, '#memoized must be called with a block'
      end

      memoized_attributes[key.to_sym] = block
      class_eval <<-RUBY, __FILE__, __LINE__ + 1
        remove_method(key) if method_defined?(key, false)
        def #{key}() self[:#{key}]; end
      RUBY
    end

    def self.memoized_attributes
      @memoized_attributes ||= {}
    end

    def [](key)
      key = key.to_sym
      if (method = self.class.memoized_attributes[key])
        super || (self[key] = instance_eval(&method))
      else

View on GitHub (pinned to b25b1b26cc)

Solutions

  1. Always pass the default-value block: memoized(:foo) { 'bar' } — the block's result is stored as memoized_attributes and the reader self[:foo] falls back to it.
  2. If you only need a plain stored attribute with no computed default, use the sibling DSL instead: options foo: :bar.
  3. When forwarding through metaprogramming, forward the block explicitly (define_method(name) { |&blk| memoized(name, &blk) } or use __method__ pass-through), because send drops blocks silently only if you forget &.
  4. Check the Faraday version's options.rb you are writing against — the DSL is marked Internal and its arity has changed between major versions.

Example fix

# before
class MyOptions < Faraday::Options
  memoized :retries   # no block
end
# => ArgumentError: #memoized must be called with a block

# after
class MyOptions < Faraday::Options
  memoized(:retries) { 2 }
end
MyOptions.from({}).retries # => 2
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'memoized requires a default block' unless block_given?
memoized(key, &block)

Type guard

null

Try / catch

begin
  memoized(key, &block)
rescue ArgumentError => e
  raise unless e.message.include?('#memoized')
  options(key => nil) # degrade to a plain attribute
end

Prevention

When it happens

Trigger: Writing class MyOptions < Faraday::Options and calling memoized :foo with no block; metaprogramming that forwards method names to memoized but drops the block (define_method(name) { memoized(name) } loses the block); copying old option-class code where the default used to be a second positional argument instead of a block.

Common situations: Library authors adding custom connection/request option classes; upgrading across Faraday versions where the Options DSL internals changed shape; using send(:memoized, key) which silently drops any block you meant to pass.

Related errors


AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21). Data as JSON: /api/errors/3e86fb8235de6031. Report an issue: GitHub.