---
title: Reflection
source: 'https://academia.sh/en/courses/object-orientation-and-metaprogramming/reflection'
course: 'Object Orientation and Metaprogramming'
language: en
updated: '2026-10-21T05:03:42+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Asks an object about its methods, instance variables and type through reflection'
    - 'Reads the arity and parameters of a method from the method object'
    - 'Explains which questions the author helper is built from'
    - 'Measures why reflection answers only partially for methods that are not written in Ruby'
---

# Reflection

Measuring how to ask an object about its own methods, state and type at run time, and how those questions classify who wrote a method.

The previous lesson showed that the same message can reach two different authors in two instances of the same class: the singleton class of a single object sits at the very front of the method lookup chain. The last sentence of that lesson left a question behind: can we ask the object itself, at run time and without opening the file, who wrote the `x` method of `Point`? This lesson answers it. **Reflection** is a program examining its own objects, classes and methods while it runs. Instead of calling a method, the program asks a question about the method.

The subject of the second half of the course is **metaprogramming**: a program that writes programs, or that changes the structure of the program while it runs. Reflection is its read-only half. It only asks and changes nothing. The next two lessons move on to writing. The axis stays the same: **who wrote this method?** There are four possible authors: programmer, ancestor, code and nobody. The programmer wrote the method with `def`. An ancestor wrote it as a superclass or a module. Code produced it: a piece of code generated the method, and no body was written by hand. Nobody means there is no definition under that name at all.

## The Classifying Helper

The file below is the shared helper of the course, and this lesson gives it as it is. It asks three questions: is there a definition under that name in the receiver's chain, does the receiver's own class carry the definition, and does the definition rest on a `def` body. Each question is a reflection call, and we will look at them one by one.

```ruby
# author.rb — a helper that classifies who wrote a method

# Classification asks in order: is there a definition in the receiver's chain at all,
# then does the receiver's own class carry it, then how was the definition produced.
def author(receiver, name)
  chain = receiver.singleton_class
  unless chain.method_defined?(name) || chain.private_method_defined?(name)
    return ["nobody", "no definition"]
  end

  definition = chain.instance_method(name)
  owner = definition.owner
  place = owner == chain ? "singleton class" : owner.to_s
  return ["ancestor", place] unless owner == receiver.class || owner == chain

  iseq = RubyVM::InstructionSequence.of(definition)
  return ["code", "#{place}, no ruby body"] if iseq.nil?
  return ["code", "#{place}, block body"] if iseq.label.start_with?("block")

  ["programmer", "#{place}, def body"]
end
```

This block is source and has no output; it is only written to disk, and the other files pull it in with `require_relative`. The helper reads only the form: it looks at where the definition sits and what kind of body it has. It does not read the intent of the author. **MP1.** This classification is meaningful only for user classes written in Ruby; the result for core classes is measured at the end of this lesson.

## The Measured Classes

The objects measured throughout the lesson are in the file below. It is an excerpt of the shared definitions of the course, with only what this lesson's observation needs: an account class, its subclass, a module and a `Struct`.

```ruby
# authors.rb — the definitions needed for this lesson's observation
require_relative "author"

module Reportable
  def report = "report: #{balance_text}"
end

class Account
  include Reportable

  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
  def net = @balance - fee

  private

  def fee = 2
end

class TermDeposit < Account
end

Point = Struct.new(:x, :y)
```

`Struct` is a core class that produces a class with the given names. Each name becomes a **member**, and a reader and a writer method for that member come ready-made. `Point` is a class produced this way. This block is source too.

## Who Wrote the Member Reader

The observation of the course in this lesson is the `x` method of a `Point` object. The file below calls the helper and prints the value the method produces next to it.

```ruby
# member_reader.rb — who wrote Point#x
require_relative "authors"

pt = Point.new(3, 4)
p author(pt, :x)
puts pt.x
```
```
["code", "Point, no ruby body"]
3
```

The author is **code**: neither the programmer wrote `x` with `def` nor a superclass did; the `Struct.new` call created it while the class was being produced. The helper concluded this from two facts. The owner of the method turned out to be `Point` itself, so it is not an ancestor. And there was no Ruby body, because `RubyVM::InstructionSequence.of` returned `nil` for this method. Which question gives which answer is measured one by one in the next sections. **MP2.** One `Struct` class was measured in Ruby 4.0.7; this measurement cannot show whether another version of `Struct` writes its members differently.

## Asking the Class and the Object

The first group of reflection asks about the structure of a class and an object: which members exist, who the superclass is, what the object holds as its own state.

```ruby
# query.rb — asking a Point object through reflection
require_relative "authors"

pt = Point.new(3, 4)
p Point.members
p pt.to_a
p Point.superclass
p author(pt, :to_a)
p author(pt, :x=)
p pt.instance_variables
```
```
[:x, :y]
[3, 4]
Struct
["ancestor", "Struct"]
["code", "Point, no ruby body"]
[]
```

`members` returned the member names in order, and `to_a` returned the values in the same order. `superclass` said that `Point` descends from `Struct`. The two `author` calls gave two different results: `Struct` wrote the `to_a` method, because the owner is not `Point`, so the author is **ancestor**. `Point` itself carries the `x=` method and it has no body, so the author is **code**. Two methods of the same object reached two different authors. The last line is surprising: `instance_variables` came out empty. `Struct` keeps its values in its own internal layout and not in instance variables, so reflection finds no `@x`. **MP3.** The empty list was measured only for `Struct`; the instance variables of an ordinary class are seen in the next block.

## Asking the State and the Private Method

In an object of an ordinary class, state lives in instance variables. Reflection lists them by name and reads their values by name. Private methods become visible when `respond_to?` is asked with a second argument of `true`.

```ruby
# state.rb — asking an object for its state and its private method
require_relative "authors"

acct = Account.new("amy", 100)
p acct.instance_variables
p acct.instance_variable_get(:@balance)
p acct.respond_to?(:fee)
p acct.respond_to?(:fee, true)
p acct.respond_to?(:net)
```
```
[:@owner, :@balance]
100
false
true
true
```

`instance_variables` returned the two instance variables in the order they were created. `instance_variable_get(:@balance)` read the value; this call works even if no reader method is defined from outside, because it reaches instance variables directly through reflection. `respond_to?(:fee)` returned `false`, because `fee` is private and the default question covers only public methods. When the second argument `true` was given, the same question returned `true`. `net` is public and returned `true`. So the default `respond_to?` question asks not whether the method exists but whether it is visible to the caller; with `true` as the second argument, private methods enter the scope. **MP4.** The private method question was measured only with `respond_to?`; other queries such as `private_methods` were not used in this lesson.

## Asking the Method Itself

Reflection can also catch a method as an object. `instance_method` gives a method object from the class, and `method` gives one from the object. A method object can be asked for its argument count, its parameters and the place it was defined, and it can be called.

```ruby
# method_query.rb — asking the method itself
require_relative "authors"

acct = Account.new("amy", 100)
p Account.instance_method(:net).arity
p Account.instance_method(:initialize).parameters
p acct.method(:net).call
p Account.instance_method(:net).source_location.nil?
p Point.instance_method(:x).source_location.nil?
p Object.const_get(:Account)
```
```
0
[[:req, :owner], [:req, :balance]]
98
false
true
Account
```

The **arity** came out as `0` for `net`: the method takes no argument. `parameters` listed the two required parameters of `initialize` with their names; `:req` means required. `acct.method(:net).call` caught the method and called it, and gave `98`: `100` minus the `2` of the private `fee`. `source_location` gives the file and line of the method; since the path depends on the environment, only whether it is empty was printed here. It is not empty for `Account#net`, because that method was written with `def`; it is empty for `Point#x`, because `Struct` wrote it and there is no source line. This is the observable counterpart of the `nil` check in `author`. The last line finds a constant from its name: `const_get(:Account)` returned the class itself. **MP5.** `source_location` was measured for only two methods; that it is not `nil` does not show by itself that the author is the programmer.

## Asking the Owner

The heart of the `author` helper is a single question: who is the owner of the method object. The owner is the class or module where the definition of the method sits. To find the owner, the helper looks at the class chain of the receiver; we can list the same chain by hand as well.

```ruby
# owner.rb — the owner of a method and the receiver's chain
require_relative "authors"

acct = Account.new("amy", 100)
td = TermDeposit.new("a", 1)
p TermDeposit.ancestors.first(3)
p acct.singleton_class.ancestors.drop(1).first(3)
p acct.method(:report).owner
p TermDeposit.instance_method(:net).owner
p author(td, :net)
p author(acct, :report)
```
```
[TermDeposit, Account, Reportable]
[Account, Reportable, Object]
Reportable
Account
["ancestor", "Account"]
["ancestor", "Reportable"]
```

`ancestors` gives the **ancestor chain** of a class from left to right: first the class itself, then the modules it includes and its superclasses, in the order the method lookup reads them. For `TermDeposit` the first three were `TermDeposit`, `Account` and `Reportable`: a module stands right behind the class that includes it. The chain of the object's own singleton class starts with the singleton class itself; that name changes in every run, so it is not printed, the first element is dropped, and the rest of the chain appears as `Account`, `Reportable`, `Object`. The owner of the `report` method came out as `Reportable`, while `TermDeposit.instance_method(:net)` reached the definition of `Account`, not of `TermDeposit`. For this reason `author(td, :net)` and `author(acct, :report)` both classified the author as **ancestor**: the owner is neither the class nor the singleton class of the receiver. **MP6.** The chain was printed with only its first three elements; the tail after `Object` was not measured.

## The Method of a Single Object

Back to the observation of the previous lesson: the same message can reach two authors in two instances of the same class. Reflection asks this too. `singleton_methods` lists only the methods defined in the object's own singleton class. The measurement below builds two `Account` objects and writes its own `net` method for only one of them.

```ruby
# singleton.rb — the same message in two instances
require_relative "authors"

a = Account.new("amy", 100)
b = Account.new("tom", 50)
def b.net = 0

p a.singleton_methods
p b.singleton_methods
p a.net
p b.net
p author(a, :net)
p author(b, :net)
```
```
[]
[:net]
98
0
["programmer", "Account, def body"]
["programmer", "singleton class, def body"]
```

The list is empty for `a`, and `net` is in it for `b`. Two instances of the same class answered the same message with two different values: `98` for `a` and `0` for `b`. The two `author` calls name the difference. The `def` in the `Account` class wrote `a.net`; a `def` wrote `b.net` too, but this time the definition sits in the singleton class of `b`, and the helper calls it **singleton class** in place of an owner name. The author is the **programmer** in both cases; the difference is in the place of the definition. Reflection therefore separates two questions: who produced the method, and in which layer does it sit. **MP7.** Only the `net` method was measured in two instances; the relation of a singleton method to a class method was not established in this lesson.

## Type and Method List

The last group asks what type an object is and what the list of a class's own methods is. The list is taken once more after the class is reopened: an `attr_reader` is added to the reopened class and the list changes.

```ruby
# kind.rb — asking an object's type and a class's methods
require_relative "authors"

acct = Account.new("amy", 100)
td = TermDeposit.new("a", 1)
p acct.is_a?(Reportable)
p td.is_a?(Account)
p td.instance_of?(Account)

class Account
  attr_reader :owner
end

p Account.public_instance_methods(false).sort
p author(acct, :owner)
```
```
true
true
false
[:balance_text, :net, :owner]
["code", "Account, no ruby body"]
```

`is_a?` returns `true` for every class and module in the chain: `acct` belongs to `Reportable`, and `td` belongs to `Account`. `instance_of?` accepts only the exact class; the `Account` question came out `false` for a `TermDeposit` instance. `public_instance_methods(false)` lists only the public methods of `Account` itself; `false` leaves out those that come from ancestors. For this reason `report`, which comes from `Reportable`, is not in the list, and neither is `initialize`, because `initialize` is private. `attr_reader :owner` added `owner` to the list. And `author` classified its author as **code**: `attr_reader` leaves no `def` body, it produces the method itself. **MP8.** The list was taken after the moment of reopening; the class is assumed not to be reopened anywhere else.

## The Bounding Measurement

So far reflection has worked on classes the programmer wrote. What happens when the helper is turned to core classes?

```ruby
# limit.rb — the limits of the rule
require_relative "authors"

p author("a", :upcase)
p author(Account, :new)
[5, :a].each do |value|
  begin
    value.singleton_class
  rescue => e
    puts "#{value.inspect} -> #{e.class}"
  end
end
p nil.singleton_class
```
```
["code", "String, no ruby body"]
["code", "Class, no ruby body"]
5 -> TypeError
:a -> TypeError
NilClass
```

For `String#upcase` and `Class#new` the helper said **code**, but this answer misleads. The core of Ruby wrote these methods with compiled code; since there is no `def` body, the rule assigned them to code, for the same reason as `Struct` members. The difference between them is invisible to the helper: one is produced at run time, the other was written in the core from the start. The rule reads the form of a definition, not its intent. The second limit was harsher: an integer and a `Symbol` value have no singleton class, and both gave a `TypeError`. `nil` returned `NilClass`, so the helper works for `nil`. The limit that follows from these three results is this: reflection says correctly where a definition sits, but it does not always say who the author is. **MP9.** The two results for core classes were measured for only these two methods; other core methods may come out differently.

## Summary

- Reflection is asking an object about its methods, state and type at run time; `members`, `instance_variables`, `respond_to?`, `is_a?` and `public_instance_methods(false)` are such questions.
- Code wrote `Point#x`: `Struct.new` created it while the class was being produced, and there is no `def` body.
- An ancestor wrote `Point#to_a`: the owner is `Struct`.
- A method object is queried with `arity`, `parameters` and `source_location`, and it is called with `call`.
- A reader added with `attr_reader` also counts as written by code.
- The helper gives a meaningful result only for user classes written in Ruby; the two measured core methods (`String#upcase`, `Class#new`) went to code.

## Next Step

In this lesson, code produced the method of a class while the class was being defined, and we read the production only from its result. The next lesson does the production itself: `define_method` defines, with a block, a method whose name is decided at run time. The same question asks, this time, how code comes to be the author.
