Section 11.2. More Advanced Techniques


11.1. Everyday OOP Tasks

Of his quick objects hath the mind no part,

Nor his own vision holds what it doth catch . . .

William Shakespeare, "Sonnet 113"

If you don't already understand OOP, you won't learn it here. And if you don't already understand OOP in Ruby, you probably won't learn it here either. If you're rusty on those concepts, you can scan Chapter 1, "Ruby in Review," where we cover it rapidly (or you can go to another book).

On the other hand, much of this current chapter is tutorial oriented and fairly elementary. So it will be of some value to the beginner and perhaps less value to the intermediate Ruby programmer. We maintain that a book is a random access storage device so that you can easily skip the parts that don't interest you.

11.1.1. Using Multiple Constructors

There is no real "constructor" in Ruby as there is in C++ or Java. The concept is certainly there because objects have to be instantiated and initialized, but the behavior is somewhat different.

In Ruby, a class has a class method new, which is used to instantiate new objects. The new method calls the user-defined special method initialize, which then initializes the attributes of the object appropriately, and new returns a reference to the new object.

But what if we want to have multiple constructors for an object? How should we handle that?

There is nothing to prevent creation of additional class methods that return new objects. Listing 11.1 shows a contrived example in which a rectangle can have two side lengths and three color values. We create additional class methods that assume certain defaults for some of the parameters. (For example, a square is a rectangle with all sides the same length.)

Listing 11.1. Multiple Constructors

class ColoredRectangle   def initialize(r, g, b, s1, s2)     @r, @g, @b, @s1, @s2 = r, g, b, s1, s2   end   def ColoredRectangle.white_rect(s1, s2)     new(0xff, 0xff, 0xff, s1, s2)   end   def ColoredRectangle.gray_rect(s1, s2)     new(0x88, 0x88, 0x88, s1, s2)   end   def ColoredRectangle.colored_square(r, g, b, s)     new(r, g, b, s, s)   end   def ColoredRectangle.red_square(s)     new(0xff, 0, 0, s, s)   end   def inspect     "#@r #@g #@b #@s1 #@s2"   end end a = ColoredRectangle.new(0x88, 0xaa, 0xff, 20, 30) b = ColoredRectangle.white_rect(15,25) c = ColoredRectangle.red_square(40)

So we can define any number of methods that create objects according to various specifications. Whether the term constructor is appropriate here is a question that we will leave to the language lawyers.

11.1.2. Creating Instance Attributes

An instance attribute in Ruby is always prefixed by an @ sign. It is like an ordinary variable in that it springs into existence when it is first assigned.

In OO languages, we frequently create methods that access attributes to avoid issues of data hiding. We want to have control over how the internals of an object are accessed from the outside. Typically we use setter and getter methods for this purpose (although in Ruby we don't typically use these terms). These are simply methods used to assign (set) a value or retrieve (get) a value, respectively.

Of course, it is possible to create these functions "by hand," as shown here:

class Person   def name     @name   end   def name=(x)     @name = x   end   def age     @age   end   # ... end


However, Ruby gives us a shorthand for creating these methods. The attr method takes a symbol as a parameter and creates the associated attribute. It also creates a getter of the same name, and if the optional second parameter is true, it creates a setter also.

class Person   attr :name, true  # Create @name, name, name=   attr :age         # Create @age, age end


The related methods attr_reader, attr_writer, and attr_accessor take any number of symbols as parameters. The first creates only "read" methods (to get the value of an attribute); the second creates only write methods (to set values); and the third creates both. For example:

class SomeClass   attr_reader :a1, :a2    # Creates @a1, a1, @a2, a2   attr_writer :b1, :b2    # Creates @b1, b1=, @b2, b2=   attr_accessor :c1, :c2  # Creates @c1, c1, c1=, @c2, c2, c2=   # ... end


Recall that assignment to a writer of this form can only be done with a receiver, so within a method the receiver self must be used.

11.1.3. Using More Elaborate Constructors

As objects grow more complex, they accumulate more attributes that must be initialized when an object is created. The corresponding constructor can be long and cumbersome, forcing us to count parameters and wrap the line past the margin.

One good way to deal with this complexity is to pass in a block to the initialize method (see Listing 11.2). We can then evaluate the block to initialize the object. The trick is to use instance_eval instead of eval to evaluate the block in the context of the object rather than that of the caller.

Listing 11.2. A "Fancy" Constructor

class PersonalComputer   attr_accessor :manufacturer,                 :model, :processor, :clock,                 :ram, :disk, :monitor,                 :colors, :vres, :hres, :net   def initialize(&block)     instance_eval &block   end   # Other methods... end desktop = PersonalComputer.new do   self.manufacturer = "Acme"   self.model = "THX-1138"   self.processor = "986"   self.clock = 9.6        # GHz   self.ram = 16           # Gb   self.disk = 20          # Tb   self.monitor = 25       # inches   self.colors = 16777216   self.vres = 1280   self.hres = 1600   self.net = "T3" end p desktop

Several things should be noted here. First, we're using accessors for our attributes so that we can assign values to them intuitively. Second, the reference to self is necessary because a setter method always takes an explicit receiver to distinguish the method call from an ordinary assignment to a local variable. Of course, rather than define accessors, we could use setter functions.

Obviously, we could perform any arbitrary logic we wanted inside the body of this block. For example, we could derive certain fields from others by computation.

Also, what if you didn't really want an object to have accessors for each of the attributes? If you prefer, you can use undef (at the bottom of the constructor block) to get rid of any or all of these. At the least, this could prevent "accidental" assignment of an attribute from outside the object.

11.1.4. Creating Class-level Attributes and Methods

A method or attribute isn't always associated with a specific instance of a class; it can be associated with the class itself. The typical example of a class method is the new method; it is always invoked in this way because it is called to create a new instance (and thus can't belong to any particular instance).

We can define class methods of our own. We have already seen this in section 11.1.1, "Using Multiple Constructors." But their functionality certainly isn't limited to constructors; they can be used for any general-purpose task that makes sense at the class level.

In this next highly incomplete fragment, we assume that we are creating a class to play sound files. The play method can reasonably be implemented as an instance method; we can instantiate many objects referring to many different sound files. But the detect_hardware method has a larger context; depending on our implementation, it might not even make sense to create new objects if this method fails. Its context is that of the whole sound-playing environment rather than any particular sound file.

class SoundPlayer   MAX_SAMPLE = 192   def SoundPlayer.detect_hardware     # ...   end   def play     # ...   end end


Notice that there is another way to declare this class method. The following fragment is essentially the same:

class SoundPlayer   MAX_SAMPLE = 192   def play     # ...   end end def SoundPlayer.detect_hardware   # ... end


The only difference relates to constants declared in the class. When the class method is declared outside its class declaration, these constants aren't in scope. For example, detect_hardware in the first fragment can refer directly to MAX_SAMPLE if it needs to; in the second fragment, the notation SoundPlayer::MAX_SAMPLE would have to be used instead.

Not surprisingly, there are class variables as well as class methods. These begin with a double @ sign, and their scope is the class rather than any instance of the class.

The traditional example of using class variables is counting instances of the class as they are created. But they can actually be used for any purpose in which the information is meaningful in the context of the class rather than the object. For a different example, see Listing 11.3.

Listing 11.3. Class Variables and Methods

class Metal   @@current_temp = 70   attr_accessor :atomic_number   def Metal.current_temp=(x)     @@current_temp = x   end   def Metal.current_temp     @@current_temp   end   def liquid?     @@current_temp >= @melting   end   def initialize(atnum, melt)     @atomic_number = atnum     @melting = melt   end end aluminum = Metal.new(13, 1236) copper = Metal.new(29, 1982) gold = Metal.new(79, 1948) Metal.current_temp = 1600 puts aluminum.liquid?        # true puts copper.liquid?          # false puts gold.liquid?            # false Metal.current_temp = 2100 puts aluminum.liquid?        # true puts copper.liquid?          # true puts gold.liquid?            # true

Note here that the class variable is initialized at the class level before it is used in a class method. Note also that we can access a class variable from an instance method, but we can't access an instance variable from a class method. After a moment of thought, this makes sense.

But what happens if you try? What if we try to print the attribute @atomic_number from within the Metal.current_temp method? We find that it seems to existit doesn't cause an errorbut it has the value nil. What is happening here?

The answer is that we're not actually accessing the instance variable of class Metal at all. We're accessing an instance variable of class Class instead. (Remember that in Ruby, Class is a class!)

Such a thing is called a class instance variable (a term that comes from Smalltalk). For more comments on this, see section 11.2.4, "Creating Parametric Classes."

Listing 11.4 summarizes the situation.

Listing 11.4. Class and Instance Data

class MyClass   SOME_CONST = "alpha"       # A class-level constant   @@var = "beta"             # A class variable   @var = "gamma"             # A class instance variable   def initialize     @var = "delta"           # An instance variable   end   def mymethod     puts SOME_CONST          # (the class constant)     puts @@var               # (the class variable)     puts @var                # (the instance variable)   end   def MyClass.classmeth1     puts SOME_CONST          # (the class constant)     puts @@var               # (the class variable)     puts @var                # (the class instance variable)   end end def MyClass.classmeth2   puts MyClass::SOME_CONST   # (the class constant)   # puts @@var               # error  out of scope   puts @var                  # (the class instance variable) end myobj = MyClass.new MyClass.classmeth1           # alpha, beta, gamma MyClass.classmeth2           # alpha, gamma myobj.mymethod               # alpha, beta, delta

We should mention that a class method can be made private with the method private_class_method. This works the same way private works at the instance level.

See also section 11.2.10, "Automatically Defining Class-level Readers and Writers."

11.1.5. Inheriting from a Superclass

We can inherit from a class by using the < symbol:

class Boojum < Snark   # ... end


Given this declaration, we can say that the class Boojum is a subclass of the class Snark, or in the same way, Snark is a superclass of Boojum. As we all know, every boojum is a snark, but not every snark is a boojum.

The purpose of inheritance, of course, is to add or enhance functionality. We are going from the more general to the more specific.

As an aside, many languages such as C++ implement multiple inheritance (MI). Ruby (like Java and some others) doesn't allow MI, but the mixin facility can compensate for this; see section 11.1.12, "Working with Modules."

Let's look at a (slightly) more realistic example. Suppose that we have a Person class and want to create a Student class that derives from it.

We'll define Person this way:

class Person   attr_accessor :name, :age, :sex   def initialize(name, age, sex)     @name, @age, @sex = name, age, sex   end   # ... end


And we'll then define Student this way:

class Student < Person   attr_accessor :idnum, :hours   def initialize(name, age, sex, idnum, hours)     super(name, age, sex)     @idnum = idnum     @hours = hours   end   # ... end # Create two objects a = Person.new("Dave Bowman", 37, "m") b = Student.new("Franklin Poole", 36, "m", "000-13-5031", 24)


Now let's look at what we've done here. What is this super that we see called from Student's initialize method? It is simply a call to the corresponding method in the parent class. As such, we give it three parameters (whereas our own initialize method takes five).

It's not always necessary to use super in such a way, but it is often convenient. After all, the attributes of a class form a superset of the attributes of the parent class, so why not use the parent's constructor to initialize them?

Concerning what inheritance really means, it definitely represents the "is-a" relationship. A Student is-a Person, just as we expect. We'll make three other observations:

  • Every attribute (and method) of the parent is reflected in the child. If Person had a height attribute, Student would inherit it, and if the parent had a method named say_hello, the child would inherit that, too.

  • The child can have additional attributes and methods, as you have already seen. That is why the creation of a subclass is often referred to as extending a superclass.

  • The child can override or redefine any of the attributes and methods of its parent.

This last point brings up the question of how a method call is resolved. How do I know whether I'm calling the method of this particular class or its superclass?

The short answer is you don't know, and you don't care. If we invoke a method on a Student object, the method for that class will be called if it exists. If it doesn't, the method in the superclass will be called, and so on. We say "and so on" because every class (except Object) has a superclass.

What if we specifically want to call a superclass method, but we don't happen to be in the corresponding method? We can always create an alias in the subclass before we do anything with it.

class Student    # reopening class   # Assuming Person has a say_hello method...   alias :say_hi :say_hello   def say_hello     puts "Hi, there."   end   def formal_greeting     # Say hello the way my superclass would.     say_hi   end end


There are various subtleties relating to inheritance that we don't discuss here, but this is essentially how it works. Be sure to refer to the next section.

11.1.6. Testing Classes of Objects

Frequently we will want to know: What kind of object is this, or how does it relate to this class? There are many ways of making a determination such as this.

First, the class method (that is, the instance method named class) always returns the class of an object. The former synonym type is obsolete.

s = "Hello" n = 237 sc = s.class    # String nc = n.class    # Fixnum


Don't be misled into thinking that the thing returned by class or type is a string representing the class. It is an actual instance of the class Class! Thus if we wanted, we could call a class method of the target type as though it were an instance method of Class (which it is).

s2 = "some string" var = s2.class             # String my_str = var.new("Hi...")  # A new string


We could compare such a variable with a constant class name to see whether they were equal; we could even use a variable as the superclass from which to define a subclass! Confused? Just remember that in Ruby, Class is an object, and Object is a class.

Sometimes we want to compare an object with a class to see whether the object belongs to that class. The method instance_of? will accomplish this. For example:

puts (5.instance_of? Fixnum)        # true puts ("XYZZY".instance_of? Fixnum)  # false puts ("PLUGH".instance_of? String)  # true


But what if we want to take inheritance relationships into account? The kind_of? method (similar to instance_of?) takes this issue into account. A synonym is is_a?, naturally enough because what we are describing is the classic is-a relationship.

n = 9876543210 flag1 = n.instance_of? Bignum     # true flag2 = n.kind_of? Bignum         # true flag3 = n.is_a? Bignum            # true flag3 = n.is_a? Integer           # true flag4 = n.is_a? Numeric           # true flag5 = n.is_a? Object            # true flag6 = n.is_a? String            # false flag7 = n.is_a? Array             # false


Obviously kind_of or is_a? is more generalized than the instance_of? relationship. For an example from everyday life, every dog is a mammal, but not every mammal is a dog.

There is one surprise here for the Ruby neophyte. Any module that is mixed in by a class maintains the is-a relationship with the instances. For example, the Array class mixes in Enumerable; this means that any array is a kind of enumerable entity.

x = [1, 2, 3] flag8 = x.kind_of? Enumerable     # true flag9 = x.is_a? Enumerable        # true


We can also use the numeric relational operators in a fairly intuitive way to compare one class to another. We say "intuitive" because the less-than operator is used to denote inheritance from a superclass.

flag1 = Integer < Numeric         # true flag2 = Integer < Object          # true flag3 = Object == Array           # false flag4 = IO >= File                # true flag5 = Float < Integer           # nil


Every class typically has a "threequal" operator === defined. The expression class === instance will be true if the instance belongs to the class. The relationship operator is usually known as the case equality operator because it is used implicitly in a case statement. This is therefore a way to act on the class of an expression.

For more information on this operator, see section 11.1.7, "Testing Equality of Objects."

We should also mention the respond_to method. This is used when we don't really care what the class is but just want to know whether it implements a certain method. This, of course, is a rudimentary kind of type information. (In fact, we might say this is the most important type information of all.) The method is passed a symbol and an optional flag (indicating whether to include private methods in the search).

# Search public methods if wumpus.respond_to?(:bite)   puts "It's got teeth!" else   puts "Go ahead and taunt it." end # Optional second parameter will search # private methods also. if woozle.respond_to?(:bite,true)   puts "Woozles bite!" else   puts "Ah, the non-biting woozle." end


Sometimes we want to know what class is the immediate parent of an object or class. The instance method superclass of class Class can be used for this.

array_parent = Array.superclass    # Object fn_parent = 237.class.superclass   # Integer obj_parent = Object.superclass     # nil


Every class except Object will have a superclass.

11.1.7. Testing Equality of Objects

All animals are equal, but some are more equal than others.

George Orwell, Animal Farm

When you write classes, it's convenient if the semantics for common operations are the same as for Ruby's built-in classes. For example, if your classes implement objects that may be ranked, it makes sense to implement the method <=> and mix in the Comparable module. Doing so means that all the normal comparison operators work with objects of your class.

However, the picture is less clear when it comes to dealing with object equality. Ruby objects implement five different methods that test for equality. Your classes might well end up implementing some of these, so let's look at each in turn.

The most basic comparison is the equal? method (which comes from Object), which returns true if its receiver and parameter have the same object ID. This is a fundamental part of the semantics of objects and shouldn't be overridden in your classes.

The most common test for equality uses our old friend ==, which tests the values of its receiver with its argument. This is probably the most intuitive test for equality.

Next on the scale of abstraction is the method eql?, which is part of Object. (Actually, eql? is implemented in the Kernel module, which is mixed in to Object.) Like ==, eql? compares its receiver and its argument but is slightly stricter. For example, different numeric objects will be coerced into a common type when compared using ==, but numbers of different types will never test equal using eql?.

flag1 = (1 == 1.0)     # true flag2 = (1.eql?(1.0))  # false


The eql? method exists for one reason: It is used to compare the values of hash keys. If you want to override Ruby's default behavior when using your objects as hash keys, you'll need to override the methods eql? and hash for those objects.

Two more equality tests are implemented by every object. The === method is used to compare the target in a case statement against each of the selectors, using selector===target. Although apparently complex, this rule allows Ruby case statements to be intuitive in practice. For example, you can switch based on the class of an object:

case an_object   when String     puts "It's a string."   when Numeric     puts "It's a number."   else     puts "It's something else entirely." end


This works because class Module implements === to test whether its parameter is an instance of its receiver (or the receiver's parents). So, if an_object is the string "cat", the expression String === an_object would be true, and the first clause in the case statement would fire.

Finally, Ruby implements the match operator =~. Conventionally this is used by strings and regular expressions to implement pattern matching. However, if you find a use for it in some unrelated classes, you're free to overload it.

The equality tests == and =~ also have negated forms, != and !~, respectively. These are implemented internally by reversing the sense of the non-negated form. This means that you implement (say) the method ==; you also get the method != for free.

11.1.8. Controlling Access to Methods

In Ruby, an object is pretty much defined by the interface it provides: the methods it makes available to others. However, when writing a class, you often need to write other helper methods, used within your class but dangerous if available externally. That is where the private method of class Module comes in handy.

You can use private in two different ways. If in the body of a class or method definition you call private with no parameters, subsequent methods will be made private to that class or module. Alternatively, you can pass a list of method names (as symbols) to private, and these named methods will be made private. Listing 11.5 shows both forms.

Listing 11.5. Private Methods

class Bank   def open_safe     # ...   end   def close_safe     # ...   end   private :open_safe, :close_safe   def make_withdrawal(amount)     if access_allowed       open_safe       get_cash(amount)       close_safe     end   end   # make the rest private   private   def get_cash     # ...   end   def access_allowed     # ...   end end

Because the attr family of statements effectively just defines methods, attributes are affected by the access control statements such as private.

The implementation of private might seem strange but is actually clever. Private methods cannot be called with an explicit receiver: They are always called with an implicit receiver of self. This means that you can never invoke a private method in another object: There is no way to specify that other object as the receiver of the method call. It also means that private methods are available to subclasses of the class that defines them but again only in the same object.

The protected access modifier is less restrictive. Protected methods can be accessed only by instances of the defining class and its subclasses. You can specify a receiver with protected methods, so you can invoke those in different objects (as long as they are objects of the same class as the sender). A common use for protected methods is defining accessors to allow two objects of the same type to cooperate with each other. In the following example, objects of class Person can be compared based on the person's age, but that age is not accessible outside the Person class:

class Person   def initialize(name, age)     @name, @age = name, age   end   def <=>(other)     age <=> other.age   end   attr_reader :name, :age   protected   :age end p1 = Person.new("fred", 31) p2 = Person.new("agnes", 43) compare = (p1 <=> p2)         # -1 x = p1.age                    # Error!


To complete the picture, the access modifier public is used to make methods public. This shouldn't be a surprise.

As a final twist, normal methods defined outside a class or module definition (that is, the methods defined at the top level) are made private by default. Because they are defined in class Object, they are globally available, but they cannot be called with a receiver.

11.1.9. Copying an Object

The Ruby built-in methods Object#clone and #dup produce copies of their receiver. They differ in the amount of context they copy. The dup method copies just the object's content, whereas clone also preserves things such as singleton classes associated with the object.

s1 = "cat" def s1.upcase   "CaT" end s1_dup   = s1.dup s1_clone = s1.clone s1                    #=> "cat" s1_dup.upcase         #=> "CAT"  (singleton method not copied) s1_clone.upcase       #=> "CaT"  (uses singleton method)


Both dup and clone are shallow copies: They copy the immediate contents of their receiver only. If the receiver contains references to other objects, those objects aren't in turn copied; the duplicate simply holds references to them. The following example illustrates this. The object arr2 is a copy of arr1, so changing entire elements, such as arr2[2] has no effect on arr1. However, both the original array and the duplicate contain a reference to the same String object, so changing its contents via arr2 also affects the value referenced by arr1.

arr1 = [ 1, "flipper", 3 ] arr2 = arr1.dup arr2[2] = 99 arr2[1][2] = 'a' arr1              # [1, "flapper", 3] arr2              # [1, "flapper", 99]


Sometimes, you want a deep copy, where the entire object tree rooted in one object is copied to create the second object. This way, there is guaranteed to be no interaction between the two. Ruby provides no built-in method to perform a deep copy, but there are a couple of techniques you can use to implement one.

The pure way to do it is to have your classes implement a deep_copy method. As part of its processing, this method calls deep_copy recursively on all the objects referenced by the receiver. You then add a deep_copy method to all the Ruby built-in classes that you use.

Fortunately, there's a quicker hack using the Marshal module. If you use marshaling to dump an object into a string and then load it back into a new object, that new object will be a deep copy of the original.

arr1 = [ 1, "flipper", 3 ] arr2 = Marshal.load(Marshal.dump(arr1)) arr2[2] = 99 arr2[1][2] = 'a' arr1              # [1, "flipper", 3] arr2              # [1, "flapper", 99]


In this case, notice how changing the string via arr2 doesn't affect the string referenced by arr1.

11.1.10. Using initialize_copy

When you copy an object with dup or clone, the constructor is bypassed. All the state information is copied.

But what if you don't want this to happen? Consider this example:

class Document   attr_accessor :title, :text   attr_reader   :timestamp   def initialize(title, text)     @title, @text = title, text     @timestamp = Time.now   end end doc1 = Document.new("Random Stuff",File.read("somefile")) sleep 300                         # Wait awhile... doc2 = doc1.clone doc1.timestamp == doc2.timestamp  # true # Oops... the timestamps are the same!


When a Document is created, it is given a time stamp. If we copy that object, we copy the time stamp also. But what if we wanted instead to capture the time that the copy operation happened?

Defining an initialize_copy makes this possible. This method is called when an object is copied. It is analogous to initialize, giving us complete control over the object's state.

class Document     # Reopen the class   def initialize_copy(other)     @timestamp = Time.now   end end doc3 = Document.new("More Stuff",File.read("otherfile")) sleep 300                          # Wait awhile... doc4 = doc3.clone doc3.timestamp == doc4.timestamp   # false # Timestamps are now accurate.


Note that the initialize_copy is called after the information is copied. That is why we omitted this line:

@title, @text = other.title, other.text


As a matter of fact, an empty initialize_copy would behave just as if the method were not there at all.

11.1.11. Understanding allocate

In rare circumstances you might want to create an object without calling its constructor (bypassing initialize). For example, maybe you have an object whose state is determined entirely by its accessors; then it isn't necessary to call mew (which calls initialize) unless you really want to. Imagine you are gathering data a piece at a time to fill in the state of an object; you might want to start with an "empty" object rather than gathering all the data up front and calling the constructor.

The allocate method was introduced in Ruby 1.8 to make this easier. It returns a "blank" object of the proper class, yet uninitialized.

class Person   attr_accessor :name, :age, :phone   def initialize(n,a,p)     @name, @age, @phone = n, a, p   end end p1 = Person.new("John Smith",29,"555-1234") p2 = Person.allocate p p1.age    # 29 p p2.age    # nil


11.1.12. Working with Modules

There are two basic reasons to use modules in Ruby. The first is simply namespace management; we'll have fewer name collisions if we store constants and methods in modules. A method stored in this way (a module method) is called with the module name; that is, without a real receiver. This is analogous to the way a class method is called. If we see calls such as File.ctime and FileTest.exist?, we can't tell just from context that File is a class and FileTest is a module.

The second reason is more interesting: We can use a module as a mixin. A mixin is like a specialized implementation of multiple inheritance in which only the interface portion is inherited.

We've talked about module methods, but what about instance methods? A module isn't a class, so it can't have instances, and an instance method can't be called without a receiver.

As it turns out, a module can have instance methods. These become part of whatever class does the include of the module.

module MyMod   def meth1     puts "This is method 1"   end end class MyClass   include MyMod   # ... end x = MyClass.new x.meth1                # This is method 1


Here MyMod is mixed into MyClass, and the instance method meth1 is inherited. You have also seen an include done at the top level; in that case, the module is mixed into Object as you might expect.

But what happens to our module methods, if there are any? You might think they would be included as class methods, but Ruby doesn't behave that way. The module methods aren't mixed in.

But we have a trick we can use if we want that behavior. There is a hook called append_features that we can override. It is called with a parameter, which is the "destination" class or module (into which this module is being included). For an example of its use, see Listing 11.6.

Listing 11.6. Including a Module with append_features

module MyMod   def MyMod.append_features(someClass)     def someClass.modmeth       puts "Module (class) method"     end     super   # This call is necessary!   end   def meth1     puts "Method 1"   end end class MyClass   include MyMod   def MyClass.classmeth     puts "Class method"   end   def meth2     puts "Method 2"   end end x = MyClass.new                       # Output: MyClass.classmeth     #   Class method x.meth1               #   Method 1 MyClass.modmeth       #   Module (class) method x.meth2               #   Method 2

This example is worth examining in detail. First, we should understand that append_features isn't just a hook that is called when an include happens; it actually does the work of the include operation. That's why the call to super is needed; without it, the rest of the module (in this case, meth1) wouldn't be included at all.

Also note that within the append_features call, there is a method definition. This looks unusual, but it works because the inner method definition is a singleton method (class-level or module-level). An attempt to define an instance method in the same way would result in a Nested method error.

Conceivably a module might want to determine the initiator of a mixin. The append_features method can also be used for this because the class is passed in as a parameter.

It is also possible to mix in the instance methods of a module as class methods. Listing 11.7 shows an example.

Listing 11.7. Module Instance Methods Becoming Class Methods

module MyMod   def meth3     puts "Module instance method meth3"     puts "can become a class method."   end end class MyClass   class << self    # Here, self is MyClass     include MyMod   end end MyClass.meth3 # Output: #   Module instance method meth3 #   can become a class method.

The extend method is useful here. This example simply becomes:

class MyClass     extend MyMod end


We've been talking about methods. What about instance variables? Although it is certainly possible for modules to have their own instance data, it usually isn't done. However, if you find a need for this capability, there is nothing stopping you from using it.

It is possible to mix a module into an object rather than a class (for example, with the extend method). See section 11.2.2, "Specializing an Individual Object."

It's important to understand one more fact about modules. It is possible to define methods in your class that will be called by the mixin. This is a powerful technique that will seem familiar to those who have used Java interfaces.

The classic example (which we've seen elsewhere) is mixing in the Comparable module and defining a <=> method. Because the mixed-in methods can call the comparison method, we now have such operators as <, >, <=, and so on.

Another example is mixing in the Enumerable module and defining <=> and an iterator each. This gives us numerous useful methods such as collect, sort, min, max, and select.

You can also define modules of your own to be used in the same way. The principal limitation is the programmer's imagination.

11.1.13. Transforming or Converting Objects

Sometimes an object comes in exactly the right form at the right time, but sometimes we need to convert it to something else or pretend it's something it isn't. A good example is the well-known to_s method.

Every object can be converted to a string representation in some fashion. But not every object can successfully masquerade as a string. That in essence is the difference between the to_s and to_str methods. Let's elaborate on that.

Methods such as puts and contexts such as #{...} interpolation in strings expect to receive a String as a parameter. If they don't, they ask the object they did receive to convert itself to a String by sending it a to_s message. This is where you can specify how your object will appear when displayed; simply implement a to_s method in your class that returns an appropriate String.

class Pet   def initialize(name)     @name = name   end   # ...   def to_s     "Pet: #@name"   end end


Other methods (such as the String concatenation operator +) are more picky; they expect you to pass in something that is really pretty close to a String. In this case, Matz decided not to have the interpreter call to_s to convert non-string arguments because he felt this would lead to too many errors. Instead, the interpreter invokes a stricter method, to_str. Of the built-in classes, only String and Exception implement to_str, and only String, Regexp, and Marshal call it. Typically when you see the runtime error TypeError: Failed to convert xyz into String, you know that the interpreter tried to invoke to_str and failed.

You can implement to_str yourself. For example, you might want to allow numbers to be concatenated to strings:

class Numeric   def to_str     to_s   end end label = "Number " + 9      # "Number 9"


An analogous situation holds for arrays. The method to_a is called to convert an object to an array representation, and to_ary is called when an array is expected.

An example of when to_ary is called is with multiple assignment. Suppose we have a statement of this form:

a, b, c = x


Assuming that x were an array of three elements, this would behave in the expected way. But if it isn't an array, the interpreter will try to call to_ary to convert it to one. For what it's worth, the method we define can be a singleton (belonging to a specific object). The conversion can be completely arbitrary; here we show an (unrealistic) example in which a string is converted to an array of strings:

class String   def to_ary     return self.split("")   end end str = "UFO" a, b, c = str     # ["U", "F", "O"]


The inspect method implements another convention. Debuggers, utilities such as irb, and the debug print method p use the inspect method to convert an object to a printable representation. If you want classes to reveal internal details when being debugged, you should override inspect.

There is another situation in which we'd like to be able to do conversions of this sort "under the hood." As a language user, you'd expect to be able to add a Fixnum to a Float, or divide a Complex number by a rational number. However, this is a problem for a language designer. If the Fixnum method + receives a Float as an argument, what can it do? It only knows how to add Fixnum values. Ruby implements the coerce mechanism to deal with this.

When (for example) + is passed an argument it doesn't understand, it tries to coerce the receiver and the argument to compatible types and then do the addition based on those types. The pattern for using coerce in a class you write is straightforward:

class MyNumberSystem   def +(other)     if other.kind_of?(MyNumberSystem)       result = some_calculation_between_self_and_other       MyNumberSystem.new(result)     else       n1, n2 = other.coerce(self)       n1 + n2     end   end end


The value returned by coerce is a two-element array containing its argument and its receiver converted to compatible types.

In this previous example, we're relying on the type of our argument to perform some kind of coercion. If we want to be good citizens, we also need to implement coercion in our class, allowing other types of numbers to work with us. To do this, we need to know the specific types that we can work with directly and convert the object to those types when appropriate. When we can't do that, we fall back on asking our parent, as in the following example:

def coerce(other)   if other.kind_of?(Float)     return other, self.to_f   elsif other.kind_of?(Integer)     return other, self.to_i   else     super   end end


Of course, for this to work, our object must implement to_i and to_f.

You can use coerce as part of the solution for implementing a Perl-like auto-conversion of strings to numbers:

class String   def coerce(n)     if self['.']       [n, Float(self)]     else       [n, Integer(self)]     end   end end x = 1 + "23"        # 24 y = 23 * "1.23"     # 29.29


We don't necessarily recommend this. But we do recommend that you implement a coerce method whenever you are creating some kind of numeric class.

11.1.14. Creating Data-only Classes (Structs)

Sometimes you need to group together a bunch of related data with no other associated processing. You could do this by defining a class:

class Address   attr_accessor :street, :city, :state   def initialize(street1, city, state)     @street, @city, @state = street, city, state   end end books = Address.new("411 Elm St", "Dallas", "TX")


This works, but it's tedious, and a fair amount of repetition is in there. That's why the built-in class Struct comes in handy. In the same way that convenience methods such as attr_accessor define methods to access attributes, class Struct defines classes that contain just attributes. These classes are structure templates.

Address = Struct.new("Address", :street, :city, :state) books = Address.new("411 Elm St", "Dallas", "TX")


So, why do we pass the name of the structure to be created in as the first parameter of the constructor and also assign the result to a constant (Address in this case)?

When we create a new structure template by calling Struct.new, a new class is created within class Struct itself. This class is given the name passed in as the first parameter and the attributes given as the rest of the parameters. This means that if we wanted, we could access this newly created class within the namespace of class Struct.

Struct.new("Address", :street, :city, :state) books = Struct::Address.new("411 Elm St", "Dallas", "TX")


After you've created a structure template, you call its new method to create new instances of that particular structure. You don't have to assign values to all the attributes in the constructor: Those that you omit will be initialized to nil. Once it is created, you can access the structure's attributes using normal syntax or by indexing the structure object as if it were a Hash. For more information, look up class Struct in any reference (such as ruby-doc.org online).

By the way, we advise against the creation of a Struct named Tms because there is already a predefined Struct::Tms class.

11.1.15. Freezing Objects

Sometimes we want to prevent an object from being changed. The freeze method (in Object) allows us to do this, effectively turning an object into a constant.

After we freeze an object, an attempt to modify it results in a TypeError. Listing 11.8 shows a pair of examples.

Listing 11.8. Freezing an Object

str = "This is a test. " str.freeze begin   str << " Don't be alarmed."   # Attempting to modify rescue => err   puts "#{err.class} #{err}" end arr = [1, 2, 3] arr.freeze begin   arr << 4                      # Attempting to modify rescue => err   puts "#{err.class} #{err}" end # Output: #   TypeError: can't modify frozen string #   TypeError: can't modify frozen array

However, bear in mind that freeze operates on an object reference, not on a variable! This means that any operation resulting in a new object will work. Sometimes this isn't intuitive. In the example below, we might expect that the += operation would fail; but it acts normally. This is because assignment is not a method call. It acts on variables, not objects; and it creates a new object as needed. The old object is indeed still frozen, but it is no longer referenced by that variable name.

str = "counter-" str.freeze str += "intuitive"       # "counter-intuitive" arr = [8, 6, 7] arr.freeze arr += [5, 3, 0, 9]      # [8, 6, 7, 5, 3, 0, 9]


Why does this happen? A statement a += x is semantically equivalent to a = a + x. The expression a + x is evaluated to a new object, which is then assigned to a! The object isn't changed, but the variable now refers to a new object. All the reflexive assignment operators exhibit this behavior, as do some other methods. Always ask yourself whether you are creating a new object or modifying an existing one; then freeze will not surprise you.

There is a method frozen? that will tell you whether an object is frozen.

hash = { 1 => 1, 2 => 4, 3 => 9 } hash.freeze arr = hash.to_a puts hash.frozen?                   # true puts arr.frozen?                    # false hash2 = hash puts hash2.frozen?                  # true


As we see here (with hash2), the object, not the variable, is frozen.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net