2.6. Methods Once Again

In order to keep the amount of chicken and egg problems in this chapter at a manageable level, we need to go back to the topic Methods and combine what we have learned so far.

Getters and Setters

As instance variables (attributes) only exist within the relevant instance, you always need to write a getter method for exporting such a variable. If we define a class Room that has the instance variables @doors and @windows (for the number of doors and windows in the room), then we can create the getter methods doors und windows (example program room.rb):
class Room
  def initialize
    @doors  = 1
    @windows = 1
  end
  
  def doors
    @doors
  end
  
  def windows
    @windows
  end
end

kitchen = Room.new

puts "D: #{kitchen.doors}"
puts "W: #{kitchen.windows}"
The execution of the program:
$ ruby room.rb 
D: 1
W: 1
$ 
As this scenario – wanting to simply return a value in identical form – is so common, there is already a ready-made getter method for it with the name attr_reader, which you would apply as follows in the program room.rb:
class Room
  def initialize
    @doors  = 1
    @windows = 1
  end
  
  attr_reader :doors, :windows
end

kitchen = Room.new

puts "D: #{kitchen.doors}"
puts "W: #{kitchen.windows}"
attr_reader is a method which is called on the Room class. That is the reason why we use Symbols (e.g. :doors and :windows) instead of variables (e.g. @doors and @windows) as parameter.

Buy the new Rails 5.1 version of this book.

attr_reader is a good example for meta programming in Ruby. When working with Rails, you will frequently come across meta programming and be grateful for how it works automagically.
If you want to change the number of doors or windows from the outside, you need a setter method. It can be implemented as follows:
class Room
  def initialize
    @doors  = 1
    @windows = 1
  end
  
  attr_reader :doors, :windows
  
  def doors=(value)       
    @doors = value
  end
  
  def windows=(value)      
    @windows = value
  end
end

kitchen = Room.new

kitchen.windows = 2     

puts "D: #{kitchen.doors}"
puts "W: #{kitchen.windows}"
The corresponding output is this:
$ ruby room.rb 
D: 1
W: 2
$ 
As you can probably imagine, there is of course also a ready-made and easier way of doing this. Via the setter method attr_writer you can simplify the code of room.rb further:
class Room
  def initialize
    @doors  = 1
    @windows = 1
  end
  
  attr_reader :doors, :windows
  attr_writer :doors, :windows
end

kitchen = Room.new

kitchen.windows = 2

puts "D: #{kitchen.doors}"
puts "W: #{kitchen.windows}"
And (who would have thought!) there is even a method attr_accessor that combines getters and setters. The code for room.rb would then look like this:
class Room
  def initialize
    @doors  = 1
    @windows = 1
  end

  attr_accessor :doors, :windows
end


kitchen = Room.new

kitchen.windows = 2

puts "D: #{kitchen.doors}"
puts "W: #{kitchen.windows}"

Built-In Methods for String

Most classes already come with a bundle of very useful methods. These methods are always written after the relevant object, separated by a point.
Here are a few examples for methods of the class String.
$ irb --simple-prompt
>> a = 'A dog'
=> "A dog"
>> a.class
=> String
>> a.size
=> 5
>> a.downcase
=> "a dog"
>> a.upcase
=> "A DOG"
>> a.reverse
=> "god A"
>> exit
$
With instance_methods(false) you can get a list of the build in methods:
$ irb --simple-prompt
>> String.instance_methods(false)
=> [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert, :length, :size, :bytesize, :empty?, :=~, :match, :succ, :succ!, :next, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :byteslice, :to_i, :to_f, :to_s, :to_str, :inspect, :dump, :upcase, :downcase, :capitalize, :swapcase, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split, :lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :prepend, :crypt, :intern, :to_sym, :ord, :include?, :start_with?, :end_with?, :scan, :ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip, :sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete, :squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :each_codepoint, :sum, :slice, :slice!, :partition, :rpartition, :encoding, :force_encoding, :valid_encoding?, :ascii_only?, :unpack, :encode, :encode!, :to_r, :to_c]
>> exit
$
If you are not sure what one of these methods does you can use ri to look it up:
$ ri -T String.size
String.size

(from ruby site)
------------------------------------------------------------------------------
  str.length   -> integer
  str.size     -> integer
   

------------------------------------------------------------------------------

Returns the character length of str.


$ 

Method Chaining

You may not think of it straight away, but once you have got used to working with Ruby, then it makes perfect sense (and is perfectly logical) to chain different methods.
$ irb --simple-prompt
>> a = 'A dog'
=> "A dog"
>> a.upcase.reverse
=> "GOD A"
>> exit
$

Converting from One to the Other: Casting

There is a whole range of useful instance methods for converting (casting) objects from one class to another. First, let's use the method .to_s to convert a Fixnum to a String.
$ irb --simple-prompt
>> a = 10
=> 10
>> a.class
=> Fixnum
>> b = a.to_s
=> "10"
>> b.class
=> String
>> exit
$

Buy the new Rails 5.1 version of this book.

Incidentally, that is exactly what puts does if you use puts to output a Fixnum or a Float (for non-strings, it simply implicitly adds the method .to_s and outputs the result).
Now we use the method .to_i to change a Float to a Fixnum.
$ irb --simple-prompt
>> c = 10.0
=> 10.0
>> c.class
=> Float
>> d = c.to_i
=> 10
>> d.class
=> Fixnum
>> exit
$

Method to_s for Your Own Classes

You should always integrate a method to_s for your own custom classes, even if it is just for the sake of easier debugging. Then you can simply output a corresponding object via puts (puts automatically outputs an object via the method to_s).
Here is an example:
$ irb --simple-prompt
>> class Person
>>   def initialize(first_name, last_name)
>>     @first_name = first_name
>>     @last_name = last_name
>>   end
>>   def to_s
>>     "#{@first_name} #{@last_name}"
>>   end
>> end
=> nil
>> sw = Person.new('Stefan', 'Wintermeyer')
=> Stefan Wintermeyer
>> puts sw
Stefan Wintermeyer
=> nil
>> exit
$

Is + a Method?

Why is there also a plus symbol in the list of methods for String? Let's find out by looking it up in ri:
$ ri -T String.+
String.+

(from ruby site)
------------------------------------------------------------------------------
  str + other_str   -> new_str
   

------------------------------------------------------------------------------

Concatenation---Returns a new String containing other_str
concatenated to str.

  "Hello from " + self.to_s   #=> "Hello from main"


$
hmmm … Let's see what it says for Fixnum:
$ ri -T Fixnum.+
Fixnum.+

(from ruby site)
------------------------------------------------------------------------------
  fix + numeric  ->  numeric_result
   

------------------------------------------------------------------------------

Performs addition: the class of the resulting object depends on the class of
numeric and on the magnitude of the result.


$
Let's have a go and play around with this in irb. So we should be able to add the + to an object, just as any other method, separated by a dot and add the second number in brackets as parameter:
$ irb --simple-prompt
>> 10 + 10
=> 20
>> 10+10
=> 20
>> 10.+10
=> 20
>> 10.+(10)
=> 20
>> exit
$
Aha! The plus symbol is indeed a method, and this method takes the next value as parameter. Really we should put this value in brackets, but thanks to Ruby's well thought-out syntax this is not necessary.

Can I Overwrite the Method +?

Yes, you can overwrite any method. Logically, this does not make much sense for methods such as +, unless you want to drive your fellow programmers mad. I am going to show you a little demo in irb so you will believe me.
The aim is overwriting the method + for Fixnum. We want the result of every addition to be the number 42.
$ irb --simple-prompt
>> 10 + 10
=> 20
>> class Fixnum
>>   def +(name, *args, &blk)
>>     42
>>   end
>> end
=> nil
>> 10 + 10
=> 42
>> exit
$
First we perform a normal addition. Than we redefine the method + for the class Fixnum, and after that we do the calculation again. But this time, with different results.