Skip to Content
Course content

16: Attr Accessors and Encapsulation

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about your microwave. On the outside, you have a few buttons: a timer, a start button, and maybe a power level selector. You can see the clock on the display, and you can change the time. But you can't just reach your finger inside the machine and manually move the magnetron or tweak the high-voltage transformer to make it heat faster. If you could, you'd probably blow a fuse—or worse.

The buttons are the "interface." The actual circuitry inside is the "implementation." In Ruby, we call this concept encapsulation. We want to hide the messy, dangerous internal state of our objects and only expose specific ways to interact with them. This prevents other parts of your program from accidentally breaking the internal logic of your class.

Why your variables are hiding from you

In Ruby, instance variables (the ones starting with @) are strictly private. If you create a User class with a @username variable, you cannot access that variable from outside the object. I remember the first time I tried this; I kept getting nil and thought I was losing my mind.

class User
  def initialize(name)
    @name = name
  end
end

me = User.new("Dave")
puts me.name # This will throw a NoMethodError!

Even though the data is right there inside the object, Ruby doesn't automatically give you a way to see it. To get that value, you'd have to write a "getter" method manually:

def name
  @name
end

Writing those one-line methods for every single variable is tedious. That's where the attr_ family of methods comes in. They are essentially shortcuts that write those boring methods for you behind the scenes.

The shortcuts: Reader, Writer, and Accessor

Depending on how much access you want to give the outside world, you have three choices. I usually default to the most restrictive one first and only open things up as needed.

  • attr_reader: This creates a "getter" method. It lets people read the value, but they can't change it. (Like the clock on the microwave).
  • attr_writer: This creates a "setter" method. It lets people change the value, but they can't read it. This is rare, but useful for things like passwords where you want to set it once and never retrieve it.
  • attr_accessor: This does both. It's a reader and a writer combined. (Like the timer button on the microwave).

Here is how that looks in a real class. Let's say we're building a simple LightBulb:

class LightBulb
  attr_reader :brand        # We can see the brand, but not change it
  attr_accessor :brightness # We can see and change the brightness

  def initialize(brand)
    @brand = brand
    @brightness = 0
  end
end

lamp = LightBulb.new("Philips")
puts lamp.brand       # Works! Output: Philips
lamp.brightness = 80  # Works! (via attr_accessor)
puts lamp.brightness  # Works! Output: 80

lamp.brand = "GE"     # Error! No writer method for brand

Protecting the guts of your object

You might be wondering, "Why not just use attr_accessor for everything?" Trust me, I've been there. It's tempting. But the goal of encapsulation is to maintain control. If you let any part of your code change any variable at any time, you'll eventually end up with a bug where a variable is set to a value that makes no sense (like a brightness of -500).

When you use a reader instead of an accessor, you're telling other developers: "You can look, but don't touch." If you need the value to change, but you want to make sure it stays within a certain range, you should skip the attr_writer and write your own custom setter method to validate the data before assigning it to the instance variable.




📋 Practical Task

Building a Secure Bank Account Wallet

You need to create a BankAccount class that prevents users from arbitrarily setting their own balance. If we used attr_accessor :balance, anyone could just type account.balance = 1000000 and rob the bank!

Your requirements:

  • Create a BankAccount class that takes an owner_name and an initial_balance during initialization.
  • The owner_name should be readable from the outside, but not changeable (use the appropriate attr_ method).
  • The balance should be readable from the outside, but not directly writable.
  • Create a method called deposit(amount) that adds the amount to the balance.
  • Create a method called withdraw(amount) that subtracts the amount from the balance, but only if the user has enough money. If they don't, print "Insufficient funds!".

Test your code by:

  1. Creating a new account for "Alice" with 100 dollars.
  2. Depositing 50 dollars.
  3. Attempting to withdraw 200 dollars (should fail).
  4. Withdrawing 30 dollars.
  5. Printing the final balance.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.