---
title: Visibility
source: 'https://academia.sh/en/courses/object-orientation-and-metaprogramming/visibility'
course: 'Object Orientation and Metaprogramming'
language: en
updated: '2026-10-21T05:03:42+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measure that a private method raises NoMethodError when called from outside and can be called from inside the object'
    - 'Show the difference between send and public_send on a private method'
    - 'Separate the behavior of a protected and a private method on another object'
    - 'Measure that changing the visibility of a method to public in a subclass changes the owner of the definition, not the author'
---

# Visibility

Public, protected and private methods; refusal of a call from outside, the difference between send and public_send, the self receiver, a protected method working on another object of the same class, and changing visibility in a subclass.

The previous lesson ended by leaving a fact: the call `account.fee` is refused from outside, yet the `net` method can call it from inside the same object. The method is the same, the author is the same, but one call is accepted and the other refused. Does the difference lie in the method itself or in whoever makes the call?

The author of this lesson is the programmer: most of the measured methods are written by the classes in their own body with `def`; accessors that are not written are handled separately in the bounding measurement. The lesson measures a second property that the author does not change: the visibility of a method. In M11/K03 we saw through packages to whom methods are open; here the same question is asked inside a single class.

## A Private Method Is Not Called from Outside

Ruby has three visibilities: public, protected and private. A public method is open to everyone. A private method, on the other hand, can be called only from inside the object itself, without an explicit receiver written. The `private` keyword makes the methods defined after it private.

```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
```

```ruby
# private_fee.rb — a private method is refused from outside
require_relative "author"

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

  def net = @balance - fee

  private

  def fee = 2
end

account = Account.new("amy", 100)
p account.net
p author(account, :fee)
begin
  account.fee
rescue NoMethodError => e
  puts e.class
end
p account.send(:fee)
begin
  account.public_send(:fee)
rescue NoMethodError => e
  puts e.class
end
p Account.private_method_defined?(:fee)
p Account.public_method_defined?(:fee)
```

```
98
["programmer", "Account, def body"]
NoMethodError
2
NoMethodError
true
false
```

`account.net` gave 98: the call from inside found `fee`. `author` gave the programmer author, so being private did not change the author. `account.fee` from outside, however, raised `NoMethodError`. Calling the same method with `send` worked and gave 2, whereas `public_send` was refused. `send` is the call that ignores visibility, and `public_send` is the one that enforces it. The last two lines ask visibility at the level of the method definition: `fee` is registered as private, not as public.

**CM9.** In this lesson "visibility" is the three-valued property that decides who may call a method: public, protected, private. Visibility does not change the author; the author says where the definition is, and visibility says whether the call is accepted.

## The self Receiver and a Private Method

The rule of a private method is a receiver rule: a call with an explicit receiver is refused. When the explicit receiver is `self`, this rule bends. The file below calls the same method in three forms.

```ruby
# self_receiver.rb — a self receiver can call a private method
class Account
  def initialize(balance)
    @balance = balance
  end

  def bare = fee
  def with_self = self.fee
  def with_other(other) = other.fee

  private

  def fee = 2
end

a = Account.new(100)
b = Account.new(50)
p a.bare
p a.with_self
begin
  a.with_other(b)
rescue NoMethodError => e
  puts e.class
end
```

```
2
2
NoMethodError
```

The call without a receiver and `self.fee` worked, and the call made through another `Account` object was refused. A private method belongs not to a class but to an object: even when it is of the same class, another object cannot reach its private method.

## A Protected Method

A method that compares two accounts needs the balance of the other side but may not want to open the balance to everyone. A protected method serves exactly this purpose: it cannot be called from outside, but another object of the same class can call it.

```ruby
# protected.rb — a protected method is open to objects of the same class
class Jar
  def initialize(amount)
    @amount = amount
  end

  def greater?(other) = amount > other.amount
  def secret_greater?(other) = secret > other.secret

  protected

  def amount = @amount

  private

  def secret = @amount
end

a = Jar.new(10)
b = Jar.new(7)
p a.greater?(b)
begin
  a.amount
rescue NoMethodError => e
  puts e.class
end
begin
  a.secret_greater?(b)
rescue NoMethodError => e
  puts e.class
end
p Jar.protected_method_defined?(:amount)
```

```
true
NoMethodError
NoMethodError
true
```

Inside `greater?` the call `other.amount` worked: a protected method is accepted on another object as long as the calling object is of the same class. `a.amount` from outside was refused. The similarly built `secret_greater?` raised `NoMethodError` on the line `other.secret`, because a private method never accepts an explicit receiver. The limit of a protected method is the class, the limit of a private method is the object.

What exactly does the "same class" criterion of a protected method cover? The file below changes the caller to a superclass, a subclass and an unrelated class.

```ruby
# protected_lineage.rb — the limit of a protected method is the lineage of its owner
class Jar
  def initialize(amount)
    @amount = amount
  end

  def greater?(other) = amount > other.amount

  protected

  def amount = @amount
end

class Gold < Jar
end

class Stranger
  def ask(jar) = jar.amount
end

p Jar.new(10).greater?(Gold.new(4))
p Gold.new(4).greater?(Jar.new(10))
begin
  Stranger.new.ask(Jar.new(1))
rescue NoMethodError => e
  puts e.class
end
```

```
true
false
NoMethodError
```

A superclass object could call the protected method of a subclass object, and a subclass object that of a superclass object (the results are `true` and `false`: 10 > 4 and 4 > 10): the criterion is that the caller descends from the class that owns the method. `Stranger` is not of this lineage, so the same call stayed closed to it. The limit of a protected method is therefore not a narrow match on a class name but the inheritance tree.

**CM10.** In this lesson "public" is a method open to everyone, "protected" is open to objects of the same class, and "private" is open only to a call without a receiver or with a `self` receiver. A call from outside with an explicit receiver is refused in every case except public.

## private Returns a Value

`private` is not only a declaration; placed in front of a method definition, it makes that definition private too. The `def` expression returns the name of the method as a symbol, and `private` takes that symbol.

```ruby
# private_def.rb — private def makes a single method private
class Access
  private def only_this = 1

  def plain = 2
end

access = Access.new
p access.plain
p Access.private_instance_methods(false)
p Access.public_instance_methods(false)
begin
  access.only_this
rescue NoMethodError => e
  puts e.class
end
```

```
2
[:only_this]
[:plain]
NoMethodError
```

`private def only_this = 1` made only that method private; the `plain` after it stayed public. Visibility can thus be written not only for a stretch of lines but for a single method.

To see the value that `private` takes directly, it is enough to print the return of the `def` expression and the return of the `private` call itself. The file below measures both.

```ruby
# private_value.rb — def returns a symbol, private returns what it received
class Access
  p(def first = 1)
  p(private def second = 2)
  p(private(:first))
end
```

```
:first
:second
:first
```

The `def` expression returned the name of the method as a symbol, and `private def` gave back the symbol it received unchanged. `private(:first)` also returned the name it took. That is why the form `private def only_this = 1` works: the inner expression produces a name, and the outer call takes that name and changes its visibility. The same contract shows that changing visibility afterwards is also a call of the form `private :name`; the place of the definition does not change, only its record does.

## Query Methods and Inheritance

Visibility appears in two more places. The first is the `respond_to?` question: whether an object responds depends on whether the call would be accepted from outside. The second is inheritance: does a private method pass to a subclass? The file below measures both.

```ruby
# query_and_inheritance.rb — respond_to? and a call from a subclass
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  private

  def fee = 2
end

class TermDeposit < Account
  def net_term = @balance - fee * 2
  def from_outside(other) = other.fee
end

v = TermDeposit.new("tom", 50)
p v.respond_to?(:fee)
p v.respond_to?(:fee, true)
p v.net_term
begin
  v.from_outside(TermDeposit.new("a", 1))
rescue NoMethodError => e
  puts e.class
end
p TermDeposit.private_method_defined?(:fee)
```

```
false
true
46
NoMethodError
true
```

`respond_to?(:fee)` gave false, whereas `respond_to?(:fee, true)` gave true: the second argument includes private methods in the question. `TermDeposit` could call `fee` without a receiver in its own body, and `net_term` gave `46`; a private method passes to the subclass, because the rule looks at the form of the call, not at the owner of the definition. But a call with an explicit receiver such as `other.fee` was refused even inside the subclass. `TermDeposit.private_method_defined?(:fee)` is true: visibility too is carried along the chain.

## A Private Setter and a Private Class Method

The bending of the receiver rule with `self` is most useful in setter methods: the form `value = 5` defines a local variable and does not call a method; to call the writer, `self.value = 5` is needed. The file below calls a private writer this way. The second part measures the visibility of class methods.

```ruby
# writer_and_class.rb — a self setter and private_class_method
class Counter
  def initialize
    @value = 0
  end

  def increment
    self.value = value + 1
    value
  end

  private

  attr_accessor :value
end

class Gadget
  def self.build = new
  private_class_method :new
end

s = Counter.new
p s.increment
p s.increment
begin
  s.value = 9
rescue NoMethodError => e
  puts e.class
end
p Gadget.build.class
begin
  Gadget.new
rescue NoMethodError => e
  puts e.class
end
```

```
1
2
NoMethodError
Gadget
NoMethodError
```

`increment` called the private `value=` setter in the form `self.value = ...`, and the counter became `1`, then `2`; `s.value = 9` from outside was refused. For class methods the same rule is applied with `private_class_method`: `Gadget.new` was refused, but `Gadget.build` produced the object because it called `new` without a receiver from inside the class itself. The only door to producing an object thus becomes a public class method.

## The Bounding Measurement

Visibility did not change the author. But when a generator such as `attr_reader` produces a private accessor, who is the author? And if a subclass makes a private method of its superclass public, whose does the method become?

```ruby
# bounding.rb — private attr_reader and public in a subclass
require_relative "author"

class Jar
  def initialize(amount)
    @amount = amount
  end

  private attr_reader :amount
end

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

  private

  def fee = 2
end

class Account3 < Account
  public :fee
end

p author(Jar.new(3), :amount)

h = Account3.new("amy", 100)
p h.fee
p h.method(:fee).owner
p author(h, :fee)
p Account.new("a", 1).respond_to?(:fee)
```

```
["code", "Jar, no ruby body"]
2
Account3
["programmer", "Account3, def body"]
false
```

Although the accessor produced with `private attr_reader :amount` is private, `author` gave the code author: the definition was produced by the `attr_reader` call and not by `def`, and in Ruby it has no body. In Ruby 4.0.7, `public :fee` in `Account3` changed two results: `h.fee` now gave 2 and the owner of the method came out as `Account3`, not `Account`. This result is understood by looking at what the visibility call does: the definition of the superclass stays as it is, and `public :fee` puts a record of the same name in the subclass. `author` counts the first record found in the chain, that record stands in `Account3`, so the programmer and `Account3, def body` came out. The superclass's own object kept answering false to the `respond_to?` question: changing visibility affects only the subclass. The measurement below tests the correctness of this explanation.

```ruby
# bounding_why.rb — public :name puts a record in the subclass
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  private

  def fee = 2
end

class Account3 < Account
  public :fee
end

h = Account3.new("amy", 100)
p Account3.instance_methods(false)
p Account.instance_method(:fee).owner
p Account3.instance_method(:fee).owner
p h.method(:fee).super_method.nil?
p Account.private_instance_methods(false)
p Account.public_method_defined?(:fee)
```

```
[:fee]
Account
Account3
true
[:fee, :initialize]
false
```

`Account3.instance_methods(false)` listed `fee`: a record was really added to the subclass, although there is not a single line of definition in its body. The definition of `Account` stayed owned by `Account`, `Account.private_instance_methods(false)` still listed it as private (the `initialize` in the list is a private method of every class), and `Account.public_method_defined?` gave false. The `super_method` of the record in the subclass came out `nil`: Ruby 4.0.7 holds this record not as a separate body but as a visibility record that looks at the definition of the superclass, and it does not count it as a second link in the chain. Visibility is not a property of the definition; it is a property of the record that finds the definition in the chain.

**CM15.** In this lesson "record" is the pair of name and visibility in the method table of a class; public and protected records are read with `instance_methods(false)`, private records with `private_instance_methods(false)`. A visibility call puts the record in the subclass; it does not copy the body of the definition.

## Which Visibility, When

The choice among the three visibilities depends on whom a method serves. The contract a class gives to the outside is its public methods: code that calls them may exist, and if the class changes them, that code breaks. In the `Account` example `net` is such a contract. `fee` is a detail that explains how `net` is computed; because it is made private, the class can change it as it likes, since no code calls it from outside.

A protected method fills a narrow gap between the two: the contract that the objects of a class use when talking to one another but that is not opened to the outside. In the `Jar` example `amount` is like this. A protected method is needed in methods that handle two objects together, such as a comparison: `greater?` read the `amount` of the other object only because it is protected. If only the object itself calls the method, making it private and calling it with a `self` receiver is enough.

Another result seen in this lesson is that visibility is not a security measure. `send` could call the private method; so visibility documents a contract and prevents an accidental call, but does not stop a deliberate one. Code that calls a private method with `send` therefore does knowingly something the class does not allow, and when the class changes that method, it is the first to break.

## Summary

- A private method cannot be called from outside with an explicit receiver and raises `NoMethodError`; it can be called from inside the object without a receiver or with a `self` receiver.
- `send` ignores visibility and gives 2; `public_send` enforces it and refuses.
- A protected method can be called on another object of the same class; a private method cannot be called on another object.
- `private def x = 1` makes only that method private; visibility is asked with `private_instance_methods(false)`.
- The author of this lesson is the programmer: for `fee` `author` gave the programmer, for `private attr_reader :amount` the code.
- `public :fee` in a subclass changes the visibility only in that class; it puts a record in the subclass, the private definition of `Account` stays in place and the owner comes out as `Account3`.

## Next Step

A `Lock` module has been mixed into a `Safe` class, and both define `unlock`. In the call `Safe.new.unlock`, which definition runs first, and how does the `super` chain follow this order? The next lesson, The Method Lookup Chain, measures this question.
