Updated October 2026: Clarified that access control is a Ruby (not Rails) feature, corrected how private and protected methods behave with subclasses and receivers, and added runnable examples plus modern idioms (private def, private attr_reader, private_constant, private_class_method).

In Ruby on Rails, access control plays a crucial role in determining which methods or attributes of a class can be accessed from outside the class. Access control is a feature of the Ruby language itself (Rails just uses it), and Ruby provides three access control levels: private, protected, and public. Let’s explore each of these levels and their implications in Rails development.

I. Private Methods

  • Definition: A private method can only be called without an explicit receiver, i.e. from inside an instance method of the object itself. Since Ruby 2.7, self.my_private_method is also allowed (a literal self receiver). Calling it on any other receiver, such as obj.my_private_method from outside, raises NoMethodError.
  • Subclasses: Private methods are inherited, so a subclass can call them (with an implicit receiver). Private in Ruby means “only the object itself”, not “only this class”.
  • Usage: Private methods are typically used for internal implementation details that should not be exposed to external code.
  • Example:
    class Account
      def initialize(balance)
        @balance = balance
      end
    
      def summary
        "Balance: #{formatted_balance}"       # implicit receiver: OK
      end
    
      def summary_with_self
        "Balance: #{self.formatted_balance}"  # literal self: OK since Ruby 2.7
      end
    
      private
    
      def formatted_balance
        format("%.2f", @balance)
      end
    end
    
    class SavingsAccount < Account
      def report
        "Savings -> #{formatted_balance}"     # subclasses can call it too
      end
    end
    
    account = Account.new(100)
    account.summary                 # => "Balance: 100.00"
    SavingsAccount.new(42).report   # => "Savings -> 42.00"
    account.formatted_balance
    # NoMethodError: private method 'formatted_balance' called for an instance of Account
    

II. Protected Methods

  • Definition: A protected method can be called with an explicit receiver, but only from inside an instance method of the same class (or a subclass), and only when the receiver is also an instance of that class family. In other words, objects of the same kind can call each other’s protected methods; outside code cannot.
  • Usage: Protected methods are typically used to compare or combine two instances of a class without making the underlying data public, e.g. in ==, <=> or eql?.
  • Example:
    class Money
      include Comparable
    
      def initialize(cents)
        @cents = cents
      end
    
      def <=>(other)
        cents <=> other.cents   # other.cents works: called from inside Money
      end
    
      protected
    
      attr_reader :cents
    end
    
    a = Money.new(500)
    b = Money.new(300)
    a > b      # => true
    a.cents
    # NoMethodError: protected method 'cents' called for an instance of Money
    

If cents were private, other.cents would raise NoMethodError, because a private method cannot be called on another object.

III. Public Methods

  • Definition: Public methods can be called from anywhere, both within and outside the class. They define the interface of the class and are accessible by any object. Methods are public by default (except initialize, which is always private).
  • Usage: Public methods are used to expose functionality that should be accessible to external code.
  • Example:
    class MyClass
      def my_public_method
        # Implementation details
      end
    end
    

IV. Modern Idioms

Besides the private / protected sections above, you can mark visibility inline:

require "digest"
require "securerandom"

class User
  ROLES = %w[admin member].freeze
  private_constant :ROLES               # User::ROLES raises NameError outside the class

  attr_reader :name
  private attr_reader :password         # Ruby 3.0+: attr_* return the method names

  def initialize(name, password)
    @name = name
    @password = password
  end

  def self.build(name)
    new(name, generate_password)
  end

  private_class_method def self.generate_password  # User.generate_password raises NoMethodError
    SecureRandom.hex(8)
  end

  private def digest                    # `def` returns the method name, so this works
    Digest::SHA256.hexdigest(password)
  end
end
  • private def ... works because def returns the method name as a symbol.
  • private attr_reader :password works since Ruby 3.0, when attr_reader/attr_writer/attr_accessor started returning an array of the defined method names and private/protected/public started accepting an array.
  • private_constant hides a constant from outside the class.
  • private_class_method makes a class method (def self.foo) private; the plain private keyword does not affect def self. methods.

V. When to Use Each Access Control Level

  • Private: Use private methods for internal implementation details that should not be accessible outside the object. In Rails, controller helpers such as set_post or post_params should be private so they are not exposed as actions.
  • Protected: Use protected methods when instances of the same class need to access each other’s internals (comparisons, equality, merging), while keeping them hidden from external code.
  • Public: Use public methods to define the interface of the class and expose functionality to external code.

VI. Conclusion

Understanding Ruby’s access control levels—private, protected, and public—provides essential insights into defining the visibility and accessibility of methods within your Rails application. By appropriately applying these access control levels, you can maintain encapsulation, ensure code integrity, and improve the overall design of your Rails application.