You learned the fundamentals of Ruby back in Chapter 1. This chapter covers some of the language’s advanced features, including modules, the Ruby object model, introspection, and a bit of metaprogramming.
Modules are used frequently in Rails applications to group similar functionality and share behavior between classes. The Ruby object model determines how methods are found and called in a hierarchy of inherited classes and shared code from modules. Introspection supports polymorphism by allowing you to look inside a class to see which methods it understands. Metaprogramming lets your classes respond to methods that don’t exist by defining methods at runtime.
Open a terminal window and launch IRB to get started. Several of the examples in this
chapter are longer than normal. You may find it easier to type the example into your editor,
save it as a file with the extension rb, and then run the example in
your terminal by entering ruby
filename.rb
. Or you can simply copy and paste the
code from your editor into IRB.
As you saw in Chapter 1, a module is a collection of
methods and constants that cannot be instantiated. You define modules in basically the
same way you define classes. Module definitions begin with the word
module
, followed by an uppercase name, and continue to the word
end
.
To demonstrate using modules, we first need a class definition. Let’s define a
simple Person
class:
class Person ➊ attr_accessor :name ➋ def initialize(name) @name = name end end
This class uses attr_accessor
➊ to define getters and
setters for the instance variable @name
, and sets the value of
@name
when created ➋.
Class names are usually nouns because they represent objects. Module names are usually
adjectives because they represent behavior. Many Ruby modules take this convention a step
further and use adjective names ending with able, such as
Comparable
and Forwardable
.
Here’s a silly example, just to show how it’s done:
module Distractable def distract puts "Ooh, kittens!" end end
Enter this module in IRB, include it in the Person
class you
created earlier in this chapter, and see if you can distract someone:
irb(main):001:0> class Person irb(main):002:1> include Distractable irb(main):003:1> end => Person irb(main):004:0> p = Person.new("Tony") => #<Person:0x007fceb1163de8 @name="Tony"> irb(main):005:0> p.distract Ooh, kittens! => nil
In Chapter 5, you also defined a module method while working with Rails
helpers. ApplicationHelper
is a module that is automatically mixed into
all controllers by Rails.
Modules serve two purposes in Ruby:
Modules are used to group related methods and prevent name conflicts.
Modules define methods that can be mixed in to classes to provide additional behavior.
Organizing your code becomes more important as your application grows. By providing namespaces and making it easy to share code between classes, modules help you break your code into manageable pieces. Let’s look at both of these purposes.
A Ruby module can be used as a namespace, a container for code such as constants or methods with related functionality.
The Math
module is an example of a built-in Ruby module used as a
namespace. It defines the constants E
and PI
as
well as many common trigonometric and transcendental methods. The double-colon operator
(::
) is used to access constants in Ruby. The following example
accesses the constant PI
in the Math
module:
irb(main):006:0> Math::PI
=> 3.141592653589793
Methods defined in a module are accessed with a dot (.
), just
like methods in a class:
irb(main):007:0> Math.sin(0)
=> 0.0
A Ruby module can also be used as a mixin to provide additional functionality to a class. Ruby only supports single inheritance; that is, a class can only inherit from a single parent class. Modules allow you to implement something similar to multiple inheritance: a class can include several modules, adding each module’s methods to its own.
You can add a module’s methods to a class in three ways, using
include
, prepend
, or extend
.
I discuss the effect of each of these keywords next.
The include
statement adds the methods from a module to a class
as instance methods and is the most common way of mixing a module into a class.
The Comparable
module, included in Ruby, is commonly
used as a mixin. It adds comparison operators and the between?
method to classes when included. The class only needs to implement the
<=>
operator. This operator compares two objects and
returns –1
, 0
, or 1
,
depending on whether the receiver is less than, equal to, or greater than the other
object.
To use this module as a mixin, add it Person
class you created
previously:
class Person ➊ include Comparable ➋ def <=>(other) name <=> other.name end end
This class now includes the Comparable
module ➊ and
defines the <=>
operator ➋ to compare the name of this
object with the name of another object.
After entering this in IRB, create a few people and see if they can be compared:
irb(main):008:0> p1 = Person.new("Tony") => #<Person:0x007f91b40140a8 @name="Tony"> irb(main):009:0> p2 = Person.new("Matt") => #<Person:0x007f91b285fea8 @name="Matt"> irb(main):010:0> p3 = Person.new("Wyatt") => #<Person:0x007f91b401fb88 @name="Wyatt"> irb(main):011:0> p1 > p2 => true
Here p1
is greater then p2
because
T is greater than M alphabetically. The
between?
method tells you whether an object falls between two
others:
irb(main):012:0> p1.between? p2, p3
=> true
In this case, between?
returns true
since
T is between M and W
alphabetically, which means it works as expected.
The prepend
statement also adds a module’s methods to a
class, but prepend
inserts the module’s methods
before the class’s methods. This means if the module
defines a method with the same name as the class, the module’s method will be
executed instead of the class’s method. Using prepend
, you
can override a method in the class by writing a method in the module with the same
name.
One practical use for prepend
is memoization.
Memoization is an optimization technique in which a program
stores the result of a calculation to avoid repeating the same calculation multiple
times.
For example, imagine you wanted to implement the Fibonacci sequence in Ruby. The first two numbers in the Fibonacci sequence are zero and one. Each subsequent number is the sum of the previous two. Here is a method to calculate the nth value of the Fibonacci sequence in Ruby:
class Fibonacci def calc(n) return n if n < 2 ➊ return calc(n - 1) + calc(n - 2) end end
Notice that the calc
method is recursive. Every call to
calc
with a value of n
greater than 1 results
in two more calls to itself ➊. Try creating an instance of this class and
calculating some small values of n
:
irb(main):013:0> f = Fibonacci.new => #<Fibonacci:0x007fd8d3269518> irb(main):014:0> f.calc 10 => 55 irb(main):015:0> f.calc 30 => 832040
As you call the method with larger values of n
, the method
takes noticeably longer to run. For values of n
around 40, the
method takes several seconds to return an answer.
The Fibonacci calc
method is slow because it repeats the same
calculations many times. But if you define a module to implement memoization, the
calculations should take significantly less time. Let’s do that now:
module Memoize def calc(n) ➊ @@memo ||= {} ➋ @@memo[n] ||= super end end
The Memoize
module also defines a calc
method. This method has a couple of interesting features. First, it initializes a
class variable named @@memo
➊ with an empty hash if it is
not already initialized. This hash stores the result of the calc
method for each value of n
. Next, it assigns the return value of
super
to @@memo
at key n
➋ if that value is not already assigned. Because we are using
prepend
to add this module into Fibonacci
,
super
calls the original calc
method defined
by the class.
Each time the calc
method is called, @@memo
stores the Fibonacci number for the value n
. For example, after
calling calc(3)
, the @@memo
hash holds these
keys and values:
{ 0 => 0, 1 => 1, 2 => 1, 3 => 2 }
On each line, the key (the first number) is the value of
n
and the value (the second number) is the corresponding
Fibonacci number. The Fibonacci number for 0 is 0, 1 is 1, 2 is 1, and 3 is 2. By
storing these intermediate values, the calc
method never needs to
perform the same calculation more than once. Use prepend Memoize
to
add the Memoize
module to the Fibonacci
class
and try it for yourself:
irb(main):016:0> class Fibonacci irb(main):017:1> prepend Memoize irb(main):018:1> end => Fibonacci irb(main):019:0> f.calc 40 => 102334155
Now that the values of calc
are being memoized, you should be
able to call calc
for greater values of n
and
get an answer almost instantly. Try it with n
= 100 or even
n
= 1000. Note that you didn’t have to restart IRB or
instantiate a new Fibonacci object. Method lookup in Ruby is dynamic.
When you use include
or prepend
to add a
module to a class, the module’s methods are added to the class as instance
methods. In Chapter 1, you learned that there are also class
methods that are called on the class itself instead of on an instance of the class.
The extend
statement adds the methods from a module as class
methods. Use extend
to add behavior to the class itself instead of
instances of the class.
The Ruby standard library includes a module named Forwardable
,
which you can use to extend a class. The Forwardable
module
contains methods useful for delegation. Delegation means relying
on another object to handle a set of method calls. Delegation is a way to reuse code
by assigning the responsibility of certain method calls to another class.
For example, imagine a class named Library
that manages a
collection of books. We store the books in an array named
@books
:
class Library def initialize(books) @books = books end end
We can store our books, but we can’t do anything with them yet. We could use
attr_accessor
to make the @books
array
available outside of the class, but that would make all of the array’s methods
available to users of our class. A user could then call methods such as
clear
or reject
to remove all of the books
from our library.
Instead, let’s delegate a few methods to the
@books
array to provide the functionality we need—a way to
get the size of the library and add a book.
1 require 'forwardable' class Library 2 extend Forwardable 3 def_delegators :@books, :size, :push def initialize(books) @books = books end end
The Forwardable
module is in the Ruby Standard Library, not the
Ruby core, so we first need to require
it ➊. Next, we use
extend
to add the Forwardable
methods to our
class as class methods ➋. Finally, we can call the
def_delegators
method ➌. The first argument to this method
is a symbol representing the instance variable to which we’re delegating
methods.
In this case, the instance variable is @books
. The rest of the
arguments are symbols representing the methods we want to delegate. The
size
method returns the number of elements in the array. The
push
method appends a new element to the end of an array.
In the following example, lib.size
initially prints 2 because
we have two books in our library. After adding a book, the size updates to 3.
irb(main):020:0> lib = Library.new ["Neuromancer", "Snow Crash"] => #<Library:0x007fe6c91854e0 @books=["Neuromancer", "Snow Crash"]> irb(main):021:0> lib.size => 2 irb(main):022:0> lib.push "The Hobbit" => ["Neuromancer", "Snow Crash", "The Hobbit"] irb(main):023:0> lib.size => 3
The Ruby object model explains how Ruby locates a method when it is called. With inheritance and modules, you may find yourself wondering exactly where a particular method is defined or, in the case of multiple methods with the same name, which one is actually invoked by a particular call.
Continuing with the simple Person
class defined previously, we
can find out a lot about this class in IRB. First, let’s see which classes and
modules define methods for the Person
class:
irb(main):024:0> Person.ancestors
=> [Person, Distractable, Comparable, Object, Kernel, BasicObject]
The class method ancestors
returns a list of classes that
Person
inherits from and the modules it includes. In this example,
Person
, Object
, and
BasicObject
are classes, whereas Distractable
,
Comparable,
and Kernel
are modules. You can find
out which of these are classes and which are modules by calling the
class
method as explained in the Class section
below.
Object
is the default root of all Ruby objects. Object inherits
from BasicObject and mixes in the Kernel module. BasicObject is the parent class of all
classes in Ruby. You can think of it as a blank class that all other classes build on.
Kernel defines many of the Ruby methods that are called without a receiver, such as puts
and exit. Every time you call puts, you’re actually calling the instance method
puts in the Kernel module.
The order of this list indicates the order in which Ruby searches for a called
method. Ruby first looks for a method definition in the class Person
and then continues looking through the list until the method is found. If Ruby
doesn’t find the method, it raises a NoMethodError
exception.
You can see a list of the class methods and instance methods defined by a class by
calling methods
and instance_methods
,
respectively. These lists include methods defined by all parent classes by default. Pass
the parameter false
to leave out only these:
irb(main):025:0> Person.methods => [:allocate, :new, :superclass, :freeze, :===, :==, ... ] irb(main):026:0> Person.methods(false) => [] irb(main):027:0> Person.instance_methods(false) => [:name, :name=, :<=>]
The Person
class contains almost 100 different class methods from
its ancestors, but it defines none of its own, so the call to
methods(false)
returns an empty array. The call to
instance_methods
returns the name
and
name=
methods defined by attr_accessor
and the
<=>
method that we defined in the body of the class.
The last piece of the object model concerns the Person
class
itself. Everything in Ruby is an object, that is, an instance of a class. Therefore, the
Person
class must be an instance of some class.
irb(main):028:0> Person.class
=> Class
All Ruby classes are instances of the class Class
. Defining a
class, such as Person
, creates an instance of the class
Class
and assigns it to a global constant, in this case
Person
. The most important method in Class
is
new
, which is responsible for allocating memory for a new object
and calling the initialize
method.
Class
has its own set of ancestors:
irb(main):029:0> Class.ancestors
=> [Class, Module, Object, Kernel, BasicObject]
Class inherits from the class Module
, which inherits from
Object
as before. The Module
class contains
definitions of several of the methods used in this section such as
ancestors
and instance_methods
.
Introspection, also known as reflection, is
the ability to examine an object’s type and other properties as a program is
running. You’ve already seen how to determine an object’s type by calling
class
and how to get a list of methods defined by an object by
calling methods
and instance_methods
, but
Ruby’s Object
class defines several more methods just for
introspecting objects. For example, given an object, you may want to determine if it
belongs to a particular class:
irb(main):030:0> p = Person.new("Tony") => #<Person:0x007fc0ca1a6278 @name="Tony"> irb(main):031:0> p.is_a? Person => true
The is_a?
method returns true
if the given class
is the class of the receiving object. In this case, it returns true
because the object p
is a Person
.
irb(main):032:0> p.is_a? Object
=> true
It also returns true
if the given class or module is an ancestor of
the receiving object. In this case, Object
is an ancestor of
Person
, so is_a?
returns
true
.
Use the instance_of?
method if you need to determine exactly which
class was used to create an object:
irb(main):033:0> p.instance_of? Person => true irb(main):034:0> p.instance_of? Object => false
The instance_of?
method returns true
only if the
receiving object is an instance of the given class. This method returns
false
for ancestors and classes inheriting from the given class. This
type of introspection is helpful in some situations, but generally you don’t need to
know the exact class used to create an object—just the object’s
capabilities.
In duck typing, you only need to know whether an object accepts the methods you need to call. If the object responds to the needed methods, you don’t have to worry about class names or inheritance. The name duck typing comes from the phrase, “If it walks like a duck and quacks like a duck, call it a duck.”
In Ruby, you can use the respond_to?
method to see if an object
responds to a particular method. If respond_to?
returns
false
, then calling the method raises a
NoMethodError
exception as explained earlier.
For example, imagine a simple method to print some information to a file with a timestamp:
def write_with_time(file, info) file.puts "#{Time.now} - #{info}" end
You can try this method in IRB.
➊ irb(main):001:0> f = File.open("temp.txt", "w") => #<File:temp.txt> ➋ irb(main):002:0> write_with_time(f, "Hello, World!") => nil ➌ irb(main):003:0> f.close => nil
First, open a File
named temp.txt in the
current directory and store the File
instance in the variable
f
➊. Then pass f
and the message
"Hello, World!"
to the write_with_time
method
➋. Finally, close the File
with f.close
➌.
The file temp.txt in the current directory now contains a single line similar to the one here:
2014-05-21 16:52:07 -0500 - Hello, World!
This method works great until someone accidentally passes a value to it that
isn’t a file, such as nil
. Here’s a possible fix for that
bug:
def write_with_time(file, info) ➊ if file.instance_of? File file.puts "#{Time.now} - #{info}" else raise ArgumentError end end
This fix solves the problem by checking to see if file
is an
instance of the File
class ➊, but it also limits the usefulness
of this method. Now it only works with files. What if you want to
write over the network using a Socket
or write to the console using
STDOUT
?
Instead of testing the type of file
,
let’s test its capabilities:
def write_with_time(file, info) ➊ if file.respond_to?(:puts) file.puts "#{Time.now} - #{info}" else raise ArgumentError end end
You know that the write_with_time
method calls the method
puts
, so check to see if file
responds to the
puts
method ➊. Now, write_with_time
works
with any data type that responds to the puts
method.
Using duck typing leads to code that can be easily reused. Look for more opportunities to apply duck typing as you build applications.
Metaprogramming is the practice of writing code that works with code instead of data. With Ruby, you can write code that defines new behavior at runtime. The techniques in this section can save you time and remove duplication from your code by allowing Ruby to generate methods when your program is loaded or as it runs.
This section covers two different ways of dynamically defining methods:
define_method
and class_eval
. It also covers
method_missing
, so you can respond to methods that haven’t been
defined.
Let’s say we have an application with a list of features that can be enabled
for users. The User
class stores these features in a hash named
@features
. If a user has access to a feature, the corresponding
hash value will be true
.
We want to add methods of the form can_
feature
!
and
can_
feature
?
to enable a feature
and check if a feature is enabled, respectively. Rather than write several mostly
identical methods, we can iterate over the list of available features and use
define_method
, as shown here, to define the individual
methods:
class User ➊ FEATURES = ['create', 'update', 'delete'] FEATURES.each do |f| ➋ define_method "can_#{f}!" do @features[f] = true end ➌ define_method "can_#{f}?" do ➍ !!@features[f] end end def initialize @features = {} end end
The User
class first creates a constant array ➊ of
available features named FEATURES
. It then iterates over
FEATURES
using each
and calls
define_method
to create a method of the form
can_
feature
!
➋ to allow a
user access to a feature. Still inside the each
block, the class also
defines a method of the form can_
feature
?
➌ that
determines whether a user has access to the feature. This method converts the value
@features[f]
to either true
or
false
by using two NOT operators ➍.
Using two NOT operators isn’t strictly necessary because the
@features
hash returns nil
for keys without
values and Ruby treats nil
as false
, but this
technique is commonly used.
Now let’s create a new User
and try the dynamically defined
methods:
irb(main):001:0> user = User.new => #<User:0x007fc01b95abe0 @features={}> irb(main):002:0> user.can_create! => true irb(main):003:0> user.can_create? => true irb(main):004:0> user.can_update? => false irb(main):005:0> user.can_delete? => false
If you want more practice with define_method
, see if you can add
methods of the form
cannot_
feature
!
,
which disables a feature for the user. More details are provided in Exercise 3 at the
end of this chapter.
The class_eval
method evaluates a string of code as if it were
typed directly into the class definition. Using class_eval
is an easy
way to add instance methods to a class at runtime.
When I discussed attr_accessor
in Chapter 1, you learned that it defines getter and setter methods
for instance variables in a class, but I didn’t discuss exactly how those methods
were defined. The attr_accessor
method is built in to Ruby. You
don’t need to define it yourself, but you can learn about
class_eval
by implementing your own version of
attr_accessor
.
➊ class Accessor ➋ def self.accessor(attr) class_eval " ➌ def #{attr} @#{attr} end ➍ def #{attr}=(val) @#{attr} = val end " end end
Here, you define a class named Accessor
➊ with a
single class method named accessor
➋. This method works like
the built-in attr_accessor
. It accepts a single parameter
representing the attribute for which you’re creating getter and setter methods.
Pass the string to class_eval
, which uses string interpolation to
insert the value of attr
as needed to define two methods. The first
method has the same name as the attribute and returns the value of the attribute
➌. The second method is the attribute name followed by an equal sign. It sets the
attribute to a specified value val
➍.
For example, if attr
is :name
, then
accessor
defines the methods name
and
name=
by replacing attr
with
name in the specified places. This is a little hard to follow
without an example. The following code uses the accessor
method in a
class:
➊ class Element < Accessor ➋ accessor :name def initialize(name) @name = name end end
First, you have the Element
class inherit from the
Accessor
class ➊ so the accessor
method
is available. Then, you pass the name of the instance variable to
accessor
➋. Here, you pass the symbol
:name
. When the program runs, the call to
class_eval
automatically generates this code inside the
Element
class:
➊ def name @name end ➋ def name=(val) @name = val end
The name
method returns the current value of the instance
variable @name
➊. The name=
method accepts
a value and assigns it to @name
➋. Test this by creating an
instance of the Element
class and trying to get and set the value of
name
:
➊ irb(main):001:0> e = Element.new "lead" => #<Element:0x007fc01b840110 @name="lead"> ➋ irb(main):002:0> e.name = "gold" => "gold" ➌ irb(main):003:0> puts e.name gold => nil
First, create a new Element
and initialize its name with
"lead"
➊. Next, use the name=
method to
assign the new name "gold"
➋. Finally, use the
name
method to display the value of @name
➌. There you have it. With a bit of metaprogramming magic, you turned lead into
gold.
Whenever Ruby can’t find a method, it calls method_missing
on the receiver. This method receives the original method name as a symbol, an array of
arguments, and any block passed to the method call.
By default, method_missing
calls super
, which
passes the method up the ancestor chain until it finds an ancestor class containing the
method. If the method reaches the BasicObject
class, it raises a
NoMethodError
exception. You can override
method_missing
by defining your own implementation in a class to
intercept these method calls and add your own behavior.
Let’s start with a simple example so you can see how it works. This class echoes any unknown method calls back to you three times:
class Echo def method_missing(name, *args, &block) word = name puts "#{word}, #{word}, #{word}" end end
Now that method_missing
is overridden, if you try to call a
nonexistent method on an instance of this class, you’ll just see that
method’s “echo” in the terminal:
irb(main):001:0> echo = Echo.new => #<Echo:0x007fa8131c9590> irb(main):002:0> echo.hello => hello, hello, hello
A real-world use for method_missing
is the Rails dynamic finder.
Using dynamic finders, you can write Active Record queries like
Post.find_by_title("First Post")
instead of
Post.where(title: "First Post").first
.
Dynamic finders can be implemented using method_missing
.
Let’s define our own version of dynamic finders. Instead of method names like
find_by_
attribute
,
we’ll use
query_by_
attribute
so we
can avoid conflicts with the built-in methods.
Open the Post
model at
app/models/post.rb in your blog directory to follow along with
this example:
class Post < ActiveRecord::Base validates :title, :presence => true has_many :comments ➊ def self.method_missing(name, *args, &block) ➋ if name =~ /Aquery_by_(.+)z/ ➌ where($1 => args[0]).first else ➍ super end end end
First, define the method_missing
class method ➊ because
our query_by_
attribute
method
will be called on the Post
class. Next, test the name against a
regular expression ➋.
Finally, call the built-in where
method ➌ using the string
captured by the regular expression and the first argument passed to the method. Be sure
to call super
➍ if the string doesn’t match; this
ensures that unknown methods will be sent to the parent class.
The regular expression /Aquery_by_(.+)z/
matches
strings that start with “query_by_” and then captures the rest of the
string using parenthesis. A full discussion of regular expressions is beyond the
scope of this book. The website
http://rubular.com/
is a great way to edit and test regular expressions
online.
The real dynamic finders also check to make sure the captured string matches an
attribute of the model. If you try to call our
query_by_
attribute
method
with nonexistent column, it raises a SQLException
.
irb(main):001:0> Post.query_by_title "First Post"
=> #<Post id: 1, ...>
Our implementation of
query_by_
attribute
has
one more problem:
irb(main):002:0> Post.respond_to? :query_by_title
=> false
Because we’re overriding method_missing
to call this
method, Ruby doesn’t know that the Post
class can respond to
it. To fix this, we need to also override the respond_to_missing?
method in the Post
model at
app/models/post.rb.
class Post < ActiveRecord::Base --snip-- def self.respond_to_missing?(name, include_all=false) ➊ name.to_s.start_with?("query_by_") || super end end
Instead of the regular expression used in method_missing
, we just
check if the method name starts with "query_by_"
➊. If it
does, this method returns true
. Otherwise, super
is called. Now restart the Rails console and try again:
irb(main):001:0> Post.respond_to? :query_by_title
=> true
With this change in place, respond_to?
returns
true
as expected. Remember to always override
respond_to_missing?
when using method_missing
.
Otherwise, users of your class have no way of knowing which methods it accepts, and the
duck typing techniques covered earlier will fail.
If you write enough Ruby, then you will eventually see all of the techniques covered in this chapter used in real-world programs. When that time comes, you can be confident that you’ll understand what the code does, instead of just assuming that metaprogramming is some kind of magic.
In the next chapter, you’ll start building a new Rails application from scratch. Along the way I’ll cover some advanced data-modeling techniques and you’ll learn even more about Active Record.
For now, try these exercises.
18.118.28.179