Hi, I’m trying to learn to use ruby debug/steep. Take the case of ActiveStorage.
I have an ActiveStorage::Attached::One called ‘content’. If I do “content.methods” or “content.inspect” I won’t see the symbol ‘blob’ or ‘attachment’, but I can do “content.blob” or “content.attachment” and get valid results. I can also do “content.blob_size” and get a blob size, even though it’s only listed explicitly if I do “content.blob.inspect”, and the type checker steep complains when I use ‘content.blob_size’ in my code.
How does this work under the hood? How do I get steep and my IDE to recognize what’s happening?
So anything Attached::One doesn’t define itself goes to the ActiveStorage::Attachment record, and anything the attachment doesn’t define goes on to its ActiveStorage::Blob. That’s why content.byte_size, content.filename, content.content_type etc. all work from the top.
delegate_missing_to is built on method_missing plus respond_to_missing?, so the methods never exist on the object. content.methods won’t list them, but content.respond_to?(:blob) returns true. Quick way to see it outside Rails:
require "active_support/all"
class Att; def blob = "B"; end
class One; delegate_missing_to :att; def att = Att.new; end
o = One.new
o.methods.include?(:blob) # => false
o.respond_to?(:blob) # => true
o.blob # => "B"
Steep can’t follow method_missing, and the activestorage signatures in gem_rbs_collection only declare a handful of methods on Attached::One (attachment, attach, attached?, url, purge…). Two ways out:
Be explicit in code: content.attachment&.blob&.byte_size. Every hop is typed, and it’s arguably clearer to read anyway.
Reopen the class in your own sig/ with just the delegated methods you use:
module ActiveStorage
class Attached::One
def blob: () -> ActiveStorage::Blob?
def byte_size: () -> Integer?
def filename: () -> ActiveStorage::Filename?
def content_type: () -> String?
end
end
RBS merges that with the collection’s definition, so you only add what’s missing. The IDE side (ruby-lsp / Steep’s LSP) picks it up from the same files.
The behavior comes from Rails’ delegation/proxy layer rather than content actually having every method as a normal instance method. ActiveStorage::Attached::One exposes attachment/blob-related behavior through generated delegation methods, so methods/inspect can be misleading compared with what you can actually call at runtime.
For Steep and IDE support, I’d look at the Rails/RBS signatures for ActiveStorage::Attached::One and the generated delegation rather than trying to infer the API from methods. If a valid runtime method such as blob_size is missing from the type information, that’s likely a typing/signature gap that should be addressed in the Rails RBS definitions.