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))
elseView on GitHub (pinned to b25b1b26cc)
Solutions
- 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.
- If you only need a plain stored attribute with no computed default, use the sibling DSL instead: options foo: :bar.
- 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 &.
- 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
- Treat Faraday::Options DSL as internal API: pin versions in CI and re-run your options specs on upgrades.
- Always pass the default block: memoized(:key) { default_value }.
- When forwarding through metaprogramming, forward blocks explicitly with &blk; plain send drops them silently.
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
- Expected :read, :write, :open. Got #{type.inspect} :(
- unknown http method: #{method}
- Unexpected params received (got #{params.size} instead of 1)
- bad argument (expected URI object or URI string)
- no stubbed request for #{env[:method]} #{env[:url]} #{env[:b
AI-assisted analysis of lostisland/faraday@b25b1b26cc (2026-08-21).
Data as JSON: /api/errors/3e86fb8235de6031.
Report an issue: GitHub.