In fact, there is a simpler and shorter way of creating a pair of get and set accessors simultaneously. All you have to do is use two special methods, attr_reader
and attr_writer
, followed by a symbol (a name preceded by a colon):
attr_reader :description attr_writer :description
You should add this code inside your class definition like this:
class Thing attr_reader :description attr_writer :description # maybe some more methods here... end
Calling attr_reader
with a symbol has the effect of creating a get accessor (here named description
) for an instance variable (@description
) with a name matching the symbol (:description
).
Calling attr_writer
similarly creates a set accessor for an instance variable. Instance variables are considered to be the “attributes” of an object, which is why the attr_reader
and attr_writer
methods are so named.
The accessors2.rb program contains some examples of attribute readers and writers in action. This is its version of the Thing class:
accessors2.rb
class Thing attr_reader :description attr_writer :description attr_writer :name def initialize( aName, aDescription ) @name = aName @description = aDescription end # get accessor for @name def name return @name.capitalize end end
Here the Thing class explicitly defines a get method accessor for the @name
attribute. The advantage of writing a complete method like this is that it gives you the opportunity to do some extra processing rather than simply reading and writing an attribute value. The get accessor, name
, uses the String.capitalize
method to return the string value of @name
with its initial letter in uppercase.
When assigning a value to the @name
attribute, I don’t need to do any special processing, so I have given it an attribute writer instead of a set
accessor method .
The @description
attribute needs no special processing at all, so I use attr_reader
and attr_writer
instead of accessor methods in order to get and set the value of the @description
variable .
Are they attributes or properties? Don’t be confused by the terminology. In Ruby, an attribute is the equivalent of what many programming languages call a property.
When you want both to read and to write a variable, the attr_accessor
method provides a shorter alternative than using both attr_reader
and attr_writer
. I have used this to access the value attribute in the Treasure class:
attr_accessor :value
This is equivalent to the following:
attr_reader :value attr_writer :value
Earlier I said that calling attr_reader
with a symbol actually creates a variable with the same name as the symbol. The attr_accessor
method also does this.
In the code for the Thing class, this behavior is not obvious since the class has an initialize
method that explicitly creates the variables. The Treasure class, however, makes no reference to the @value
variable in its initialize
method:
class Treasure < Thing attr_accessor :value def initialize( aName, aDescription ) super( aName, aDescription ) end end
The only indication that @value
exists at all is this accessor definition:
attr_accessor :value
My code at the bottom of the accessors2.rb source file sets the value of each Treasure object as a separate operation, following the creation of the object itself, like this:
t1.value = 800
Even though it has never been formally declared, the @value
variable really does exist, and you are able to retrieve its numerical value using the get accessor: t1.value
. To be absolutely certain that the attribute accessor really has created @value
, you can always look inside the object using the inspect
method. I have done so in the final two code lines in this program:
puts "This is treasure1: #{t1.inspect}" puts "This is treasure2: #{t2.inspect}"
This displays the data inside the t1 and t2 objects, including the @value
variables:
This is treasure1: #<Treasure:0x33a6c88 @value=100, @name="sword", @description="an Elvish weapon forged of gold (now somewhat tarnished)"> This is treasure2: #<Treasure:0x33a6c4c @value=500, @name="dragon horde", @description="a huge pile of jewels">
Attribute accessors can initialize more than one attribute at a time if you send them a list of symbols separated by commas, like this:
accessors3.rb
attr_reader :name, :description attr_writer(:name, :description) attr_accessor(:value, :id, :owner)
As always, parentheses around the arguments are optional but, in my view (for reasons of clarity), are to be preferred.
Now let’s see how to put attribute readers and writers to use in my adventure game. Load the 2adventure.rb program. You will see that I have created two readable attributes in the Thing class: name
and description
. I have also made description
writeable; however, because I don’t plan to change the names of any Thing objects, the name
attribute is not writeable:
2adventure.rb
attr_reader( :name, :description ) attr_writer( :description )
I have created a method called to_s
, which returns a string describing the Treasure object. Recall that all Ruby classes have a to_s
method as standard. The Thing.to_s
method overrides (and replaces) the default one.
def to_s # override default to_s method return "(Thing.to_s):: The #{@name} Thing is #{@description}" end
You can override existing methods when you want to implement new behavior appropriate to the specific class type.