44. exit 0
@@results.each do |r|
if searchtype == 'h'
puts quot;#{r.price} url=#{r.url}quot;
puts quot;#{r.stars} #{r.name} $#{r.loprice} - $#{r.hiprice}quot;
elsif searchtype == 'f'
puts quot;#{r.price} url=#{r.url}quot;
r.legs.each do |leg|
puts quot; #{leg}quot;
end
end
end
exit(0)
more = poll_results_file(searchtype)
@@results.each do |r|
puts quot;#{r.price} #{r.url}quot;
r.legs.each do |leg|
puts quot; #{leg}quot;
end
end
end
Whoa.
46. I Want to:
find flights from CLT to RDU leaving today
and returning in one week
47. I Want to:
find :flights, :from => :CLT, :to => :RDU,
:leaving => Date.today, :returning => Date.today + 7
48. 48
40
The Old Way
sid = getsession(@@token)
searchid = start_flight_search(sid, ‘n’, ‘CLT’, ‘RDU’, Date.today, nil, 1)
more = poll_results('f', sid, searchid, nil)
while more == 'true' do
more = poll_results('f', sid, searchid, nil)
sleep(3)
end
def poll_results(searchtype, sid, searchid, count)
url = quot;/s#{@@sparkleinstance}/apibasic/flight?searchid=#{searchid}&apimode=1&_sid_=#{sid}quot;
more = nil
Net::HTTP.start(@@hostname, @@port) do |http|
if count
url += quot;&c=#{count}quot;
end
response = http.get(url)
body = response.body
File.open(quot;ksearchbody.xmlquot;, quot;wquot;) do |f|
f.puts(body)
end
more = handle_results(searchtype, body)
if more != 'true'
# save the body, so we can test without doin
# an actual search
File.open(quot;ksearchresults.xmlquot;, quot;wquot;) do |f|
f.puts(body)
end
end
end
return more
end
49. 48
40
The Old Way, cont.
def handle_results(searchtype, body)
xml = REXML::Document.new(body)
more = xml.elements['/searchresult/morepending']
@@lastcount = xml.elements['/searchresult/count'].text
@@sparkleinstance = xml.elements['/searchresult/searchinstance'].text
if more
more = more.text
end
if more != 'true'
@@results = []
#puts quot;count=#{@@lastcount}quot;
xml.elements.each(quot;/searchresult/trips/tripquot;) do |e|
trip = Trip.new()
e.each_element(quot;pricequot;) do |t|
trip.price = t.text
trip.url = t.attribute(quot;urlquot;)
end
e.each_element(quot;legsquot;) do |legs|
legs.each_element(quot;legquot;) do |l|
leg = Leg.new
l.each_element do |ld|
# extract the detail from each leg
case
when ld.name == 'airline': leg.airlinecode = ld.text
#...
end
end
trip.legs << leg
end # leg in legs loop
end # legs in trip loop
#e.each_element(quot;/searchresult/trips/trip/pricequot;) { |p| trip.price = p.text }
#puts quot;trip: #{trip.price}quot;
@@results << trip
end # each trip
end
return more
end
51. 40
Expectations
class KayakTest < Test::Unit::TestCase
def test_find_should_call_out_to_session_endpoint
setup_mocks_for_find
Kayak.find :flights
end
private
def setup_mocks_for_find
response = mock(:body => '<?xml version=quot;1.0quot;?>
<ident>
<uid>uid</uid>
<sid>0123456789</sid>
<token>12345</token>
<error></error>
</ident>')
success = mock()
success.expects(:get).with('/k/ident/apisession?token=12345').returns
(response)
Net::HTTP.expects(:start).at_least_once.yields(success)
end
end
52. 40
Parsing Responses
class KayakTest < Test::Unit::TestCase
def test_session_response_should_be_parsed_for_session_id
setup_mocks_for_find
Kayak.find :flights
assert_equal '0123456789', Kayak.session_id
end
private
def setup_mocks_for_find
response = mock(:body => '<?xml version=quot;1.0quot;?>
<ident>
<uid>uid</uid>
<sid>0123456789</sid>
<token>12345</token>
<error></error>
</ident>')
success = mock()
success.expects(:get).with('/k/ident/apisession?token=12345').returns
(response)
Net::HTTP.expects(:start).at_least_once.yields(success)
end
end
53. class Kayak
First Cut
@@session_id = nil
@@search_id = nil
@@search_options = {}
TOKEN = '12345'
HOSTNAME = 'www.kayak.com'
PORT = 80
class << self
def session_id
@@session_id
end
def method_missing(name, *args)
@@search_options[name]
end
def find(type, conditions = {})
session_id ||= initialize_session
@@search_options[:origin] = conditions[:from]
@@search_options[:destination] = conditions[:to]
@@search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving]
@@search_options[:leave_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning]
search_id ||= initialize_search
self
end
def initialize_search
Net::HTTP.start(HOSTNAME, PORT) do |http|
response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&...
if body = response.body
xml = REXML::Document.new(body)
@@search_id = xml.elements['//searchid'].text
end
end
end
def initialize_session
Net::HTTP.start(HOSTNAME, PORT) do |http|
response = http.get(quot;/k/ident/apisession?token=#{TOKEN}quot;)
if body = response.body
xml = REXML::Document.new(body)
@@session_id = xml.elements['//sid'].text
end
end
end
end
end
54. module Kayak
Second Cut
TOKEN = '12345'
HOSTNAME = 'www.kayak.com'
PORT = 80
class Flight
attr_accessor :session, :search_id, :search_options
def method_missing(name, *args)
search_options[name]
end
def initialize(conditions = {})
session ||= Kayak::Session.new
self.search_options = {}
self.search_options[:origin] = conditions[:from]
self.search_options[:destination] = conditions[:to]
self.search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving]
self.search_options[:return_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning]
Net::HTTP.start(HOSTNAME, PORT) do |http|
response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&return_date=...
self.search_id = Kayak.retrieve(response, '//searchid')
end
end
def self.find(args)
self.new(args)
end
end
class Session
attr_accessor :session_id
def initialize
Net::HTTP.start(Kayak::HOSTNAME, Kayak::PORT) do |http|
response = http.get(quot;/k/ident/apisession?token=#{Kayak::TOKEN}quot;)
self.session_id ||= Kayak.retrieve(response, '//sid')
end
end
end
def self.find(type, *args)
case type
when :flights
Kayak::Flight.find(*args)
end
end
...
55. I Want to:
find :flights, :from => :CLT, :to => :RDU,
:leaving => Date.today, :returning => Date.today + 7
62. I Want to:
find :flights, :from => :CLT, :to => :RDU,
:leaving => Date.today, :returning => Date.today + 7
63. Blocks for All
In other languages, you have to specify
explicitly that a function can accept another
function as an argument. But in Ruby, any
method can be called with a block as an
implicit argument.
Matz, 2003