4.5. Objects and Classes

Ruby wouldn't be much of an object-oriented language if you weren't able to define your own classes and objects. The next few sections show you how.

4.5.1. Defining and Instantiating Classes

Classes are defined through the class keyword, followed by the capitalized name of the class. The name needs to be capitalized because, as mentioned before, classes in Ruby are constants:

class Account
end

As usual, the definition is terminated by end, and any line of code contained between class and end forms the body of the class. From the defined class you can obtain an object by invoking the new method:

account = Account.new
account.class                 # Account

By employing the Object#is_a? method, you can determine whether or not an object is an instance of a given class:

account.is_a? Account         # true

The same method can also be used to verify if a class is a superclass of the class of an instance (or an ancestor class in the inheritance hierarchy):

account.is_a? Object          # true

The preceding line tells you that Object is a superclass or an ancestor for the Account class. It's actually a superclass as you can see if you run the following:

account.class.superclass      # Object

You may notice that no method is specified in the Account class, but it was still possible to instantiate it thanks to the fact that the constant Account is a Class object, and as such, it has access to a new method for creating instances.

4.5.1.1. The initialize Method

An empty class defined in this manner won't be very useful, so the next step is to specify an initialize method for the class. This method is private by default, and therefore cannot be called directly as you would with a constructor in C# or VB (but you can call it from within the class implementation). If an initialize method exists, this will automatically be called by the method new when it creates an instance of the given class.

For the sake of simplicity, assume that in order to open an account, all that's required is a name and Social Security number:

class Account
  def initialize(holder, ssn)

@holder = holder
    @ssn = ssn
    @balance = 0
  end
end

Note that the method requires two arguments. The arguments passed to the new method will be passed to the initialize method. Therefore, by requiring two arguments for the initialize method, you are essentially specifying that the new method will require two arguments as well:

account = Account.new("Jane Smith", "123-45-6789")

By default, whenever you define a method with the def keyword followed by a simple identifier inside a class, this is an instance method. As such, it is accessible only to objects of that class (and its subclasses).

4.5.1.2. self

Within the body of the initialize method, you set three instance variables. Instance variables are prefixed by an @ symbol and can only be accessed by instance methods of that object. Ruby uses self as a reference to the current object. This is somewhat similar to this in C# and Me in VB, but can be used anywhere in the code of your program. To clarify this, take a look at how self changes depending on where you are in the execution of the code:

class A
  puts self

  def initialize
    puts self
  end
end

a = A.new

This will print A first, and then something along the lines of #<A:0x3e09054>. Within the class definition, but outside of any method, self references the class itself, whereas inside an instance method, self references the current instance. It is important to point this out because as a .NET developer, you may be inclined to "declare" your instance variables at the beginning of the class outside of any method, which would not be what you intended.

4.5.2. Attributes and Accessor Methods

Now that you've specified a more meaningful initialization, you can inspect the object that you've created and verify that the instance variables are being set correctly:

account = Account.new("Jane Smith", "123-45-6789")
# Prints #<Account:0x3c18ed4 @balance=0, @ssn="123-45-6789", @holder="Jane Smith">
puts account.inspect

Now think about adding a couple of instance methods for depositing and withdrawing money from the account as shown in Listing 4-5 (account.rb).

Example 4.5. Adding Deposit and Withdrawal to the Account Class
class Account
  def initialize(holder, ssn)
    @holder = holder
    @ssn = ssn
    @balance = 0
  end

  def deposit(amount)
    @balance += amount
    puts "You deposited #{amount} dollars."
  end

  def withdrawal(amount)
    new_balance = @balance - amount
    if new_balance >= 0
      @balance = new_balance
      puts "You withdrew #{amount} dollars."
    else
      puts "Your account doesn't allow overdrafts."
    end
  end
end

account = Account.new("Jane Smith", "123-45-6789")
account.deposit(100)     # Prints You deposited 100 dollars.
account.withdrawal(20)   # Prints You withdrew 20 dollars.
account.withdrawal(100)  # Prints Your account doesn't allow overdrafts.

Note that if you tried the following:

puts account

you'd get an unconvincing #<Account:0x3c18678> written to the console. This is due to the fact that puts is using the default Object#to_s method, thereby printing the object's class and the encoding of its object id.

Change this by overwriting the default with your own to_s instance method:

def to_s
  "#{@holder}'s account has #{@balance} dollars."
end

Now, puts account prints "Jane Smith's account has 80 dollars." Note how easy it is to overwrite methods defined in the parent class: you simply redefine them.

You may also be tempted to read the balance with the dot notation directly from your instance, but this won't work:

account.balance   # Raises a NoMethodError

Instance variables are associated with a given instance, but are not directly accessible.

Instance variables are essentially private, unlike constants, which are practically public.

This guarantees proper encapsulation while still allowing instance methods to be read and modified. If you want to be able to read the balance of a given account, you could define a getter method in the class. Getter and setter methods in Ruby can be declared as normal instance methods. In the case of @balance you don't want to change it directly, so it will be the equivalent of a read-only property:

def balance
  @balance
end

You may also want to retrieve the name of the account holder. Assume for a moment that Jane Smith is getting married to John Doe, and opts to change her surname. To handle this occurrence as well, you should be able to define a getter and a setter method for the @holder variable. This is easy enough and can be done as follows:

def holder
  @holder
end

def holder=(new_name)
  @holder = new_name
end

NOTE

You may be tempted to use the newly defined setter method from within other instance methods. For example, instead of using @holder = new_holder you may opt for holder = new_holder. Don't do that. This will only create a holder local variable, not actually set the value of the instance variable @holder. For simple scenarios stick with @holder = new_holder. If the setter method does a lot of heavy lifting for you and you'd like to reuse it, use self.holder = new_holder instead.

Now you can do the following:

puts account.holder   # Prints Jane Smith
account.holder = "Jane Doe"
puts account.holder   # Prints Jane Doe

This is okay; it works as expected, and it wasn't too much of a hassle. You essentially defined the equivalent of traditional properties in C# or VB, with a much cleaner and concise syntax. Ruby pushes the envelope further when it comes to attributes though, and provides a series of methods to automate this extremely common process, whenever the logic of the getter and setter are trivial (as it was in this case). C# 3.0 assumes a similar approach with its recently introduced auto-implemented properties.

An attribute is Ruby-speak for an instance variable that is available through a getter or setter method. They are what C# and Visual Basic programmers call properties, and have nothing to do with the word "attribute" in the .NET world.

You can specify that an attribute should be read only (a getter) through the attr_reader method, written only (a setter) through the attr_writer method, and readable and writable (an accessor) through the attr_accessor method. All three methods accept a list of symbols that specify the attributes that should be defined. They can also — but much less conventionally — accept a list of strings.

These methods are defined by the Module class, which is a superclass for the Class class. As such they are available for every class in Ruby. This is an example of Ruby's powerful metaprogramming abilities. With a single method you're able to dynamically create instance variables, plus getter and setter methods.

In the wake of this new knowledge, you can rewrite your class as shown in Listing 4-6 (account2.rb).

Example 4.6. Using attr_reader and attr_accessor in the Account Class
class Account
  attr_reader :balance, :ssn
  attr_accessor :holder

  def initialize(holder, ssn)
    @holder = holder
    @ssn = ssn
    @balance = 0
  end

  def deposit(amount)
    @balance += amount
    puts "You deposited #{amount} dollars."
  end

  def withdrawal(amount)
    new_balance = @balance - amount
    if new_balance >= 0
      @balance = new_balance
      puts "You withdrew #{amount} dollars."
    else
      puts "Your account doesn't allow overdrafts."
    end
  end

  def to_s
    "#{@holder}'s account has #{@balance} dollars."
  end
end

account = Account.new("Jane Smith", "123-45-6789")
puts account
puts account.balance
account.deposit(1000)
puts account.balance
account.withdrawal(100)
puts account.balance
puts account.holder
account.holder = "Jane Doe"
puts account.holder
puts account.ssn

This prints to the standard output:

Jane Smith's account has 0 dollars.
0
You deposited 1000 dollars.
1000
You withdrew 100 dollars.
900
Jane Smith
Jane Doe
123-45-6789

4.5.3. Methods Visibility

Ruby's methods can be public, protected, or private. Methods are by default public, with an exception for the initialize method and methods defined globally, outside of a class definition. What this means is that whenever you define a new method in a class, be it an instance method or a class method (as shown later on), these can be invoked on the object and on the class, respectively.

Private methods are methods intended for use from within the class or its subclasses, and as such cannot be invoked outside of the class (or its subclasses). Even from within the class or object implementation, these are implicitly invoked on self (for example, my_private_method not self.my_private_method or obj.my_private_method).

Protected methods are a middle ground. They can only be used from within the class and its subclasses, and allow for invocation on a receiver other than the implicit self, as long as the objects they are invoked on are the same class or subclass of self's class. Of the three visibility levels, protected is by far the least commonly used (being utilized in practice only for instance methods).

Method visibility can be set in two ways. You can use the private, public, and protected methods without arguments, for example:

class A
  def a
  end

  def b
  end

  protected

  def c
  end

  def d
  end

  def e
  end

  private

def f
  end
end

a and b will be public by default; all the methods underneath protected and up to private will be protected (meaning c, d, and e). f will be private. You'll be able to run:

obj = A.new
obj.a
obj.b

but not so with any of the other methods previously defined:

obj.c # protected method 'c' called for #<A:0x3d68a50> (NoMethodError)

The second way to set method visibility is by specifying arguments for any of three methods seen previously:

class A
  def a
  end

  def b
  end

  def c
  end

  def d
  end

  def e
  end
  protected :c, :d, :e

  def f
  end
  private :f
end

Ruby's reflective nature and metaprogramming capabilities actually allow you to work around the method visibility limits and access methods that are defined as private (and instance variables). Exploring these capabilities is outside the scope of this book, but keep in mind that the encapsulation provided by these three levels of visibility does not limit Ruby's incredibly reflective and open nature.

4.5.4. Single Inheritance

Ruby supports single inheritance. A class has only one direct parent. Every class you define is implicitly a subclass of the Object class and, therefore, its instances are provided with a whole set of features out of the box (for example, you already saw the to_s method).

When programming in C# or VB you have the concept of "multiple implementations" through interfaces, to emulate the benefits of multiple inheritance. Ruby uses something called mixins, as explained later on.

If you want to specify that class Child inherits from class Parent, you can use the < operator:

class Child < Parent
end

If Parent exposes many useful methods and attributes, Child will automatically inherit them "for free." This is what Rails uses for controllers and models. A controller looks like the following when you've just created it:

class PostsController < ApplicationController
end

And this is an Active Record model:

class Post < ActiveRecord::Base
end

Now imagine that you have two classes, Child and Parent, defined as follows:

class Parent
  def my_method
    puts "I'm in Parent and self is #{self}"
  end

  def parent_method
    puts "I'm in the method defined in Parent only and self is #{self}"
  end
end

class Child < Parent
  def my_method
    puts "I'm in the overwritten Child method and self is #{self}"
  end
end

The parent_method is defined by Parent only, but it's still accessible by instances of its subclass Child. The fact that my_method is defined by both the Child class and its superclass (Parent) implies that the respective objects call the version defined by their own class:

child = Child.new
parent = Parent.new

child.my_method
child.parent_method

parent.my_method
parent.parent_method

This prints:

I'm in the overwritten Child method and self is #<Child:0x3e08a28>
I'm in the method defined in Parent only and self is #<Child:0x3e08a28>
I'm in Parent and self is #<Parent:0x3e089d8>
I'm in the method defined in Parent only and self is #<Parent:0x3e089d8>

Also notice how self is always a reference to the current object, no matter where the method that is invoked from is defined.

In the previous section you defined a bank account class. This was the definition of its initialize method:

class Account
  # ...

  def initialize(holder, ssn)
    @holder = holder
    @ssn = ssn
    @balance = 0
  end

  # ...
end

If you were to define a class to represent a credit card account, you could inherit from the more generic Account, but you would have to specify an Annual Percentage Rate (APR) for the account (obviously, this is really simplifying the banking system here). So you might be tempted to write the following:

class CreditCard < Account
  # ...
  def initialize(holder, ssn, interest_rate)
    @holder = holder
    @ssn = ssn
    @balance = 0
    @apr = interest_rate
  end
  # ...
end

cc = CreditCard.new("Jane Smith", "123-45-6789", 12.99)

This works but it's not very DRY. The two initialize methods would be essentially identical if it wasn't for the @apr assignment. If the logic of the method changes in the future, you'd have to go and change the same code in both classes. Ruby's solution to this is the method super, as shown here:

class CreditCard < Account
  # ...
  def initialize(holder, ssn, interest_rate)
    super(holder, ssn)
    @apr = interest_rate
  end
  # ...
end

When you specify super(holder, ssn) in the initialize method, Ruby looks for an initialize method in the superclass, and passes the two arguments specified to it. In this case it executes the code within Account's initialize method, which sets both @holder and @ssn, and then "comes back" to the method that called it, to continue the execution (in this case, by performing an assignment to the instance variable @apr). When the method that Ruby is looking for does not exist in the immediate superclass, Ruby continues to search for it in each ancestor within the inheritance hierarchy until it finds one that implements it (and raises an error if there are no classes that implement it). The exact method resolution algorithm is more complex than this, though, and is explained in detail later in this chapter.

4.5.5. Monkey Patching

If you've worked with .NET, you're probably familiar with the concept of sealed classes. C# uses the sealed modifier, whereas Visual Basic .NET uses NotInheritable; they both mean the same thing: this class shall not be inherited from.

In Ruby things are much more open and dynamic. Not only can you inherit from any user-defined class, core classes, and classes defined in the Core and Standard libraries, but you can actually reopen classes and redefine existing methods or add your own methods and attributes, without touching the initial definition of the class.

ActiveSupport

Rails' internal code relies heavily on ActiveSupport, a collection of utility classes and Ruby's Standard Library extensions, which contains numerous useful features. ActiveSupport is possible thanks to Ruby's ability to reopen classes. The following are a few basic examples of methods that are available only when the ActiveSupport library has been loaded into your Ruby programs (and of course, ActiveSupport is automatically available in Rails applications):

require 'rubygems'
require 'activesupport'

puts "my_table".classify    # Prints MyTable
puts "author_id".humanize   # Prints Author
puts "mouse".pluralize      # Prints mice

puts 2.days.ago             # Prints something like Mon Jun 30
19:02:29 -0400 2008
puts 3.hours.from_now       # Prints something like Wed Jul 02
22:02:29 -0400 2008

As you can see ActiveSupport is not only for Rails' internal code, but you can use it in your own programs and in Rails applications whenever it is convenient to do so.


Remember the Integer class (inherited by both Fixnum and Bignum) discussed in the previous chapter? Now add an even? instance method to it:

class Integer
  def even?
    self & 1 == 0
  end
end

Believe it or not, you can now do the following:

puts 10.even?   # Prints true
puts 15.even?   # Prints false

Notice how self is used to refer to the current instance within the method even?, to verify whether or not it's an even number.

Perhaps you'd like a squares iterator:

class Integer
  def squares
    self.times {|x| yield x**2 }
  end
end

10.squares {|n| print n, " " } # Prints 0 1 4 9 16 25 36 49 64 81

Or plug that each_even iterator you defined before for ranges, directly into the Array class (for a bit of variety):

class Array
  def each_even
    self.each do |n|
      yield n if (n&1).zero?
    end
  end
end

[1,1,2,4,5,6,8,9,10,11,15].each_even {|x| print x, " " }   # Prints 2 4 6 8 10

As you can see, this can be a very powerful feature and most people who learn about it for the first time think it's extremely cool. Most Rails plugins rely on this technique (called Monkey Patching) to modify the core behavior of Rails, which would otherwise be hard to customize. Monkey Patching is a useful technique and in the Ruby community, unlike the Python one, it is usually not frowned upon. As a developer you should always keep a balanced approach to programming though.

This technique can be easily abused, thus making it hard to find bugs. For example, a library could change the behavior of a core method and your program — which loads the library and relies on the standard behavior of the method — will start to act differently from what you expected, for no apparent reason.

With great power comes great responsibility. Always try to see if other techniques are suitable before blindly applying Monkey Patching, which may appear to be the "easy way out."

4.5.6. Singleton Methods and Eigenclasses

In Ruby you can define methods that exist only for a specific instance. These methods are called singleton methods, for example:

str = "A string"

def str.print
  puts self
end

str.print  # Prints A string

# Raises a NoMethodError
"a different one".print

Duck Typing

Monkey Patching is only one of the unusual names that you'll hear in the Ruby community. Another common one is Duck Typing.

The gist of it is that in Ruby there is a tendency to consider objects based on their behavior, as opposed to their type. In other words, more often than not, we care more about what an object can do and what methods it implements, instead of its type. The saying goes, "If it walks like a duck, and it talks like a duck, then it's a duck." It may not be an actual duck, but Ruby is able to treat it as such.

If you define the following method:

def put_them_together(a, b)
  a + b
end

it doesn't particularly matter if a and b are two integers, two floats, an integer and a float, two complex numbers, two points of a plane, two strings, two arrays...as long as they can be "added" through the + method/operator.

At times you may want to be more mindful and verify that a given object responds to a certain method before trying to invoke it. The method respond_to? does just that:

10.respond_to?(:find)           # false
"a string".respond_to?(:find)   # true
[1,2,3].respond_to?(:find)      # true
(5..50).respond_to?(:find)      # true


As you can see, the method name is prefixed by the actual object that you're defining it for (plus a dot). If you define such a method for a particular object and then try to call it for a different instance of the same class, the method won't be available and a NoMethodError will be raised.

Please note that the main Ruby implementation treats fixnums and symbols as immediate values (they are still objects, but are treated as values as opposed to references). This generally doesn't affect you in any way, but there is a small caveat; being immediate values, you won't be able to define a singleton method for any of their objects. What's more, for consistency reasons, the same is extended to any instance of the Numeric class, thus:

a = 3

def a.print
  puts self
end

a.print   # Raises a TypeError

4.5.6.1. Class Methods

Having introduced singleton methods naturally brings us to class methods. Class methods are singleton methods defined for an object that happens to be a class (or a module). In fact, in Ruby, even classes are objects, because they are themselves instances of the Class class. What does this mean in practice?

Class methods must be invoked on a class (or a module), instead of an instance of the class. For example, when you invoke File.open you are requesting the open class method on the class File, similarly to how Math.cos means invoking the class method cos defined by the module Math. On the other hand, "a string".reverse or ["a", "string"].join(' ') are calls to instance methods on receivers that are "regular objects."

Class methods in Ruby are essentially the same as static methods in C# and shared methods in VB.NET. Given that class methods are just a particular form of singleton methods, the syntax for defining them is quite different from the one in those languages though. You can define them as follows:

class MyClass
  def self.my_name
    puts self.to_s
  end
end

MyClass.my_name   # Prints MyClass

The object you are defining the singleton method my_name for is referenced by self. But as you have seen before, self inside of a class and outside of an instance method refers to the class itself. So what you are doing is the equivalent of saying: def MyClass.my_name (which would work as well, by the way). Now you can call the my_name method on MyClass. Note also how self within the class method just defined is still referencing MyClass, because there is no instance, and the "current object" is the class itself, which acts as the receiver of the method invocation.

Class Variables Considered Harmful

Similarly to "static methods," Ruby provides "static fields" as well. Ruby offers class variables, which are variables defined at class level and prefixed by two @@ symbols (for example, @@counter).

Whenever a subclass modifies the value of a class variable though, this is changed for the base class and all of its subclasses as well; this is not always the desired outcome and this lack of encapsulation can accidentally introduce hard-to-trace bugs into the program.

Their usage is often discouraged in the Ruby community, and many avoid them altogether, opting for class instance variables instead. These are instance variables (a single @) that are defined at class level. Googling the subject should bring up a series of discussions on the topic.

On a side note, global variables prefixed by the $ sign (for example, $my_directory) are accessible in any scope within an application and their use tends to be discouraged as well.

You can use class variables if you have to, even Rails uses them internally, but do so with care.


As you would probably expect, invoking a class method of a class on an instance of that class raises an error:

m = MyClass.new
m.my_name   # Raises a NoMethodError

4.5.6.2. Eigenclasses

Whenever you define a singleton method for a particular object, this gets stored in an anonymous class associated with that object. This special type of class is often called eigenclass, but can also be referred to as singleton class, metaclass, or even ghost class.

Ruby offers a shorthand notation for explicitly opening the eigenclass of a given object:

class << obj
  #...
end

Any method defined within that eigenclass will of course be a singleton method for obj, as shown in the following example:

beats = ["Ginsberg",  "Kerouac",  "Burrough",  "Corso"]

class << beats
  def list
    self.join(", ")
  end
end

puts beats.list     # Prints Ginsberg, Kerouac, Burrough, Corso
puts [1,2,3].list   # Raises a NoMethodError

This is a handier way of defining several singleton methods at once without having to prefix each definition with obj. (for example, obj.method1, obj.method2, and so on).

A distinction between singleton methods defined on common objects and those defined on class objects (hence class methods) is that the eigenclasses associated with a class object can have a superclass, plus ancestor classes. Understanding this distinction will help you better understand Ruby's lookup method algorithm, as explained later in this chapter.

The same technique can be applied to define class methods en masse (remember, class methods are singleton methods for a class object). Assuming that you defined a MyClass class, you can define new class methods for it by opening its eigenclass:

class << MyClass
  def method1
    # ...
  end

  def method2
    # ...
  end

def method3
    # ...
  end
end

MyClass.method1
MyClass.method2
MyClass.method3

Or perhaps more commonly:

class MyClass
  # ...
  # Some instance methods
  #...

  # Class methods
  class << self
    def method1
      # ...
    end

    def method2
      # ...
    end

    def method3
      # ...
    end
  end
end

MyClass.method1
MyClass.method2
MyClass.method3

Notice how class << self is equivalent to class << MyClass because self within that point of the class definition references to MyClass.

Again, this is shorthand that spares you from defining each class method by prefixing it with self.:

# Equivalent to the previous definition
class MyClass
  # ...
  # Some instance methods
  # ...

  # Class methods
  def self.method1
    # ...
  end

  def self.method2
    # ...
  end

def self.method3
    # ...
  end
end

MyClass.method1
MyClass.method2
MyClass.method3

The class << self idiom is extremely common, but a style consideration is in order. When you define several singleton methods at once directly in the eigenclass, you may have code that spans many lines, so it may not be so obvious that a method you're looking at is a class method (because it's defined between class << self and end) and not an instance method. For the sake of readability it is usually better to define class methods explicitly by prefixing them with self.

As you advance with your knowledge of Ruby, you'll realize that the ability to open eigenclasses still comes in handy on a few occasions, especially when doing metaprogramming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.68