At Well House Consultants, we provide
training courses on
subjects such as Perl, Python, Tcl/Tk, PHP and MySQL. We're asked (and answer)
many questions, and answers to those which are of general interest are
published in this area of our site.
>> link to
our solutions centre home page
>> link to
Ruby - More Classes and Objects>> link to
Ruby Miscellany>> link to
resource index - Ruby
MODULES IN RUBY
As your program and range of names grows, you may find that
you have code that's going to cause potential name space
conflicts - repeated names. That was someting that OO
elegantly solves for classes of objects, but perhaps you
want the equivalent of a class that doesn't instantiate
anything?
You're looking for a NAMESPACE that's loaded in - probably
with a REQUIRE from a MODULE. Example to follow .... but
we can't stress the importance of namespaces and modules as
your application grows. Not put too strongly, all the
standard stuff in the libraries is in modules, and it's a
very good ides for you to mimic the principle.
Here's an example module:
module Taxcalc
VAT_RATE = 17.5
def Taxcalc.net(gross)
net = gross / (100.0 + VAT_RATE) * 100.0
return net
end
def Taxcalc.tax(gross)
tax = gross - net(gross)
end
end
and a program to use it:
require "taxcalc"
amount = 70.50
shopkeeper = Taxcalc.net(amount)
vatman = Taxcalc.tax(amount)
print "#{vatman} to the taxman\n"
print "#{shopkeeper} to the supplier\n"
print "Tax rate is #{Taxcalc::VAT_RATE}\n"
Note that we preced methods with "modulename." so that
they're just like a static method, and we preced variables
within the module with :: - not something you'll do all the
time, but useful (in our example) to get directly at the
VAT rate.
The programs runs as you would hope:
earth-wind-and-fire:~/ruby/r119 grahamellis$ ruby wot.rb
10.5 to the taxman
60.0 to the supplier
Tax rate is 17.5
earth-wind-and-fire:~/ruby/r119 grahamellis$
MIXINS
If you include a module within a class, then the members of
that module also become available as methods in the class.
This is a superb way to add in functionality to a whole lot
of classes, and in essence does away with any need for
multiple inheritance in very much the same way that Java's
interfaces to away with the need for multiple inheritance in
that language.
Let's define an application with a a class of invoice
objects, and then mix in an updated tax module that defines
instance methods as well as module/class methods.
Here's the updates tax module - remember, this is going o be
mixed in so it contains a number of methods I want to pull
into my Invoice class but no actual objects.
module Tcalc
VAT_RATE = 17.5
def getnet
net = @howmuch / (100.0 + VAT_RATE) * 100.0
return net
end
def gettax
tax = @howmuch - self.getnet
end
end
And here's the code that calls in those methods into the
Invoice class
require "tcalc"
class Invoice
include Tcalc # The line that mixes
def initialize(what,howmuch)
@what = what
@howmuch = howmuch
end
def getgross
return @howmuch
end
def getwhat
return @what
end
end
bills = [Invoice.new("Stefano",240.00),
Invoice.new("Andy",180.00),
Invoice.new("Lionel",80),
Invoice.new("Bruce",60)]
account_line = "%-20s %8.2f %8.2f %8.2f\n"
acl2 = account_line.gsub(".2f","s")
ntot = ttot = gtot = 0.0
print acl2 % ["Descripton", "net", "tax", "gross"]
print acl2 % ["", "=====", "=====", "====="]
bills.each do |bill|
person = bill.getwhat
ntot += (net = bill.getnet)
ttot += (tax = bill.gettax)
gtot += (gross = bill.getgross)
print account_line % [person, net, tax, gross]
end
print acl2 % ["", "=====", "=====", "====="]
print account_line % ["TOTAL", ntot, ttot, gtot]
And the result of running that code
earth-wind-and-fire:~/ruby/r119 grahamellis$ ruby mixdem.rb
Descripton net tax gross
===== ===== =====
Stefano 204.26 35.74 240.00
Andy 153.19 26.81 180.00
Lionel 68.09 11.91 80.00
Bruce 51.06 8.94 60.00
===== ===== =====
TOTAL 476.60 83.40 560.00
earth-wind-and-fire:~/ruby/r119 grahamellis$
USING THE COMPARABLE MIXIN
One of the big uses of mixins in Ruby is to bring in
standard groups of methods to your own class. A really good
example of this is the Comparable module .... if you want to
redefine the six operators < <= > >= == and <=> in a class
that you've written, then you can do so, or you can make
your life easy by using Comparable and just redefining <=>.
Let's compare the delivery order of some letters for Postman
Pat, who has gone high Tech and has Ruby doing his sorting
for him ... with the assitance of Jess the cat.
"This demonstration defines a class of objects
of type Delivery which are house numbers and the
name of the recipient. The postman needs to see
which object to deliver first and which second,
and in order to do so he goes up the even numbers
then back down the odd numbers.
For readers who are not from the UK, British streets
are usually numbered back and forth across the road
so that you have 1,3,5,7 etc on one side and 2,4,6,8
etc on the other. And with modern busy traffic, the
postman does NOT want to keep crossing!!"
class Delivery
include Comparable
def initialize(number,name)
@name=name
@number=number
end
def getnumber
@number
end
def <=> (partner)
print "I'll ask Jess\n"
return -1 if @number.modulo(2) != 0 and partner.getnumber.modulo(2) == 0
return 1 if @number.modulo(2) == 0 and partner.getnumber.modulo(2) != 0
return partner.getnumber <=> @number if @number.modulo(2) == 0
return @number <=> partner.getnumber
end
def to_s
"Deliver to #{@name} at #{@number}"
end
end
gordon = Delivery.new(10,"Mr Dodge")
graham = Delivery.new(404,"Mr Ellis")
reggie = Delivery.new(22,"Dr Dodson")
kim = Delivery.new(25,"Ms Ellis")
chris = Delivery.new(15,"Mr Ellis")
print kim," before ",chris,"\n" if kim < chris
print chris," before ",kim,"\n" if kim >= chris
print reggie," before ",chris,"\n" if reggie < chris
print chris," before ",reggie,"\n" if reggie >= chris
print reggie," before ",graham,"\n" if reggie < graham
print graham," before ",reggie,"\n" if reggie >= graham
print reggie," before ",gordon,"\n" if reggie < gordon
print gordon," before ",reggie,"\n" if reggie >= gordon
print reggie," before ",kim,"\n" if reggie < kim
print kim," before ",reggie,"\n" if reggie >= kim
And running the program:
earth-wind-and-fire:~/ruby/r119 grahamellis$ ruby pat.rb
I'll ask Jess
I'll ask Jess
Deliver to Mr Ellis at 15 before Deliver to Ms Ellis at 25
I'll ask Jess
I'll ask Jess
Deliver to Mr Ellis at 15 before Deliver to Dr Dodson at 22
I'll ask Jess
I'll ask Jess
Deliver to Mr Ellis at 404 before Deliver to Dr Dodson at 22
I'll ask Jess
Deliver to Dr Dodson at 22 before Deliver to Mr Dodge at 10
I'll ask Jess
I'll ask Jess
I'll ask Jess
Deliver to Ms Ellis at 25 before Deliver to Dr Dodson at 22
earth-wind-and-fire:~/ruby/r119 grahamellis$
See also
Well House Consultants provides training courses ... that's our main business ...
and from time to time we write additional technical notes and articles to widen
the issues covered for a particular group of trainees. Longer articles of more
general interest are published here in our
solutions centre.
You'll also find shorter items at
The Horse's Mouth and
delegate's questions answered at
the Opentalk forum.