Книга: Metaprogramming Ruby 2
Назад: Chapter 3: Tuesday: Methods
Дальше: Dynamic Methods

A Duplication Problem

Where you and Bill face a problem with duplicated code.

Today, your boss asked you to work on a program for the accounting department. They want a system that flags expenses greater than $99 for computer gear, so they can crack down on developers splurging with company money. (You read that right: $99. The purchasing department isn’t fooling around.)

Some other developers already took a stab at the project, coding a report that lists all the components of each computer in the company and how much each component costs. To date, they haven’t plugged in any real data. Here’s where you and Bill come in.

The Legacy System

Right from the start, you have a challenge on your hands: the data you need to load into the already established program is stored in a legacy system stuck behind an awkwardly coded class named DS (for “data source”):

 
class​ DS
 
def​ initialize ​# connect to data source...
 
def​ get_cpu_info(workstation_id) ​# ...
 
def​ get_cpu_price(workstation_id) ​# ...
 
def​ get_mouse_info(workstation_id) ​# ...
 
def​ get_mouse_price(workstation_id) ​# ...
 
def​ get_keyboard_info(workstation_id) ​# ...
 
def​ get_keyboard_price(workstation_id) ​# ...
 
def​ get_display_info(workstation_id) ​# ...
 
def​ get_display_price(workstation_id) ​# ...
 
# ...and so on

DS#initialize connects to the data system when you create a new DS object. The other methods—and there are dozens of them—take a workstation identifier and return descriptions and prices for the computer’s components. With Bill standing by to offer moral support, you quickly try the class in irb:

 
ds = DS.new
 
ds.get_cpu_info(42) ​# => "2.9 Ghz quad-core"
 
ds.get_cpu_price(42) ​# => 120
 
ds.get_mouse_info(42) ​# => "Wireless Touch"
 
ds.get_mouse_price(42) ​# => 60

It looks like workstation number 42 has a 2.9GHz CPU and a luxurious $60 mouse. This is enough data to get you started.

Double, Treble… Trouble

You have to wrap DS into an object that fits the reporting application. This means each Computer must be an object. This object has a single method for each component, returning a string that describes both the component and its price. Remember that price limit set by the purchasing department? Keeping this requirement in mind, you know that if the component costs $100 or more, the string must begin with an asterisk to draw people’s attention.

You kick off development by writing the first three methods in the Computer class:

 
class​ Computer
 
def​ initialize(computer_id, data_source)
 
@id = computer_id
 
@data_source = data_source
 
end
 
 
def​ mouse
 
info = @data_source.get_mouse_info(@id)
 
price = @data_source.get_mouse_price(@id)
 
result = ​"Mouse: ​#{info}​ ($​#{price}​)"
 
return​ ​"* ​#{result}​"​ ​if​ price >= 100
 
result
 
end
 
 
def​ cpu
 
info = @data_source.get_cpu_info(@id)
 
price = @data_source.get_cpu_price(@id)
 
result = ​"Cpu: ​#{info}​ ($​#{price}​)"
 
return​ ​"* ​#{result}​"​ ​if​ price >= 100
 
result
 
end
 
 
def​ keyboard
 
info = @data_source.get_keyboard_info(@id)
 
price = @data_source.get_keyboard_price(@id)
 
result = ​"Keyboard: ​#{info}​ ($​#{price}​)"
 
return​ ​"* ​#{result}​"​ ​if​ price >= 100
 
result
 
end
 
 
# ...
 
end

At this point in the development of Computer, you find yourself bogged down in a swampland of repetitive copy and paste. You have a long list of methods left to deal with, and you should also write tests for each and every method, because it’s easy to make mistakes in duplicated code.

“I can think of two different ways to remove this duplication,” Bill says. “One is a spell called Dynamic Methods. The other is a special method called method_missing. We can try both solutions and decide which one we like better.” You agree to start with Dynamic Methods and get to method_missing after that.

Назад: Chapter 3: Tuesday: Methods
Дальше: Dynamic Methods