1. Computing
Send to a Friend via Email

Using Attributes

By

Using Attributes

Look at any object oriented code and it all more or less follows the same pattern. Create an object, call some methods on that object and access attributes of that object. There's not much else you can do with an object except pass it as a parameter to another object's method. But what we're concerned with here is attributes.

Attributes are like instance variables you can access via the object dot notation. For example, person.name would access a person's name. Similarly, you can often assign to attributes like person.name = "Alice". This is a similar feature to member variables (such as in C++), but not quite the same. There's nothing special going on here, attributes are implemented in most languages using "getters" and "setters," or methods that retrieve and set the attributes from instance variables.

Ruby doesn't make a distinction between attribute getters and setters and normal methods. Because of Ruby's flexible method calling syntax, no distinction needs to be made. For example, person.name and person.name() are the same thing, you're calling the name method with zero parameters. One looks like a method call and the other looks like a attribute, but they're really both the same thing. They're both just calling the name method. Similarly, any method name that ends in an equals sign (=) can be used in an assignment. The statement person.name = "Alice" is really the same thing as person.name=(alice), even though there is a space in between the attribute name and the equals sign, it's still just calling the name= method.

Implementing Attributes Yourself

You can easily implement attributes yourself. By defining setter and getter methods, you can implement any attribute you wish. Here's some example code implementing the name attribute for a person class. It stores the name in a @name instance variable, but the name doesn't have to be the same. Remember, there's nothing special about these methods.


#!/usr/bin/env ruby

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

  def name
    @name
  end

  def name=(name)
    @name = name
  end

  def say_hello
    puts "Hello, #{@name}"
  end
end

One thing you'll notice right away is that this is a lot of work. It's a lot of typing just to say that you want an attribute named name that accesses the @name instance variable. Luckily, Ruby provides some convenience methods that will define these methods for you.

Using attr_reader, attr_writer and attr_accessor

There are three methods in the Module class that you can use inside of your class declarations. Remember that Ruby makes no distinction between runtime and "compile time," and any code inside of class declarations can not only define methods but call methods as well. Calling the attr_reader, attr_writer and attr_accessor methods will in turn define the setters and getters we were defining ourselves in the previous section.

The attr_reader method does just like what it sounds like it will do. It takes any number of symbol parameters and, for each parameter, defines a "getter" method that returns the instance variable of the same name. So, we can replace our name method in the previous example with attr_reader :name.

Similarly, the attr_writer method defines a "setter" method for each symbol passed to it. Note that the equals sign need not be part of the symbol, only the attribute name. We can replace the name= method from the previous example with a call to attr_writier :name.

And, as expected, attr_accessor does the job of both attr_writer and attr_reader. If you need both a setter and getter for an attribute, it's common practice not to call the two methods separately, and instead call attr_accessor. We could replace both the name and name= methods from the previous example with a single call to attr_accessor :name.


#!/usr/bin/env ruby

def person
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def say_hello
    puts "Hello, #{@name}"
  end
end

Why Define Setters and Getters Manually?

Why should you define setters manually? Why not use the attr_* methods every time? Because they break encapsulation. Encapsulation is the principal that states no outside entity should have unrestricted access to the internal state of your objects. Everything should be accessed using an interface that prevents the user from corrupting the internal state of the object. Using the methods above, we've punched a big hole in our encapsulation wall and allowed absolutely anything to be set for a name, even obviously invalid names.

One thing you'll often see is that attr_reader will be used to quickly define a getter, but a custom setter will be defined since the internal state of the object often wants to be read directly from the internal state. The setter is then defined manually and does checks to ensure that the value being set makes sense. Or, perhaps more commonly, no setter is defined at all. The other methods in the class function set the instance variable behind the getter in some other way.

We can now add an age and properly implement a name attribute. The age attribute can be set in the constructor method, read using the age getter but only manipulated using the have_birthday method, which will increment the age. The name attribute has a normal getter, but the setter makes sure the name is capitalized and is in the form of Firstname Lastname.


#!/usr/bin/env ruby

class Person
  def initialize(name, age)
    self.name = name
    @age = age
  end

  attr_reader :name, :age

  def name=(new_name)
    if new_name =~ /^[A-Z][a-z]+ [A-Z][a-z]+$/
      @name = new_name
    else
      puts "'#{new_name}' is not a valid name!"
    end
  end

  def have_birthday
    puts "Happy birthday #{@name}!"
    @age += 1
  end

  def whoami
    puts "You are #{@name}, age #{@age}"
  end
end

p = Person.new("Alice Smith", 23)

# Who am I?
p.whoami

# She got married
p.name = "Alice Brown"

# She tried to become an eccentric musician
p.name = "A"
# But failed

# She got a bit older
p.have_birthday

# Who am I again?
p.whoami

©2014 About.com. All rights reserved.