tapi 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +152 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/tapi.rb +9 -0
- data/lib/tapi/v3/client.rb +261 -0
- data/lib/tapi/v3/configurable.rb +27 -0
- data/lib/tapi/v3/errors.rb +26 -0
- data/lib/tapi/v3/flights/search.rb +124 -0
- data/lib/tapi/v3/generic_search.rb +97 -0
- data/lib/tapi/v3/hotels/search.rb +213 -0
- data/lib/tapi/v3/utils.rb +64 -0
- data/lib/tapi/v3/validations.rb +58 -0
- data/spec/client_spec.rb +320 -0
- data/spec/configurable_spec.rb +27 -0
- data/spec/flights/search_spec.rb +102 -0
- data/spec/hotels/search_spec.rb +143 -0
- data/spec/spec_helper.rb +12 -0
- metadata +201 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
gem "activesupport", "~> 2.3.5"
|
3
|
+
gem 'curb', "~> 0.7.10"
|
4
|
+
gem 'json', "~> 1.5.1"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.3.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (2.3.11)
|
5
|
+
curb (0.7.10)
|
6
|
+
diff-lcs (1.1.2)
|
7
|
+
git (1.2.5)
|
8
|
+
jeweler (1.5.2)
|
9
|
+
bundler (~> 1.0.0)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
rake
|
12
|
+
json (1.5.1)
|
13
|
+
rake (0.8.7)
|
14
|
+
rcov (0.9.9)
|
15
|
+
rspec (2.3.0)
|
16
|
+
rspec-core (~> 2.3.0)
|
17
|
+
rspec-expectations (~> 2.3.0)
|
18
|
+
rspec-mocks (~> 2.3.0)
|
19
|
+
rspec-core (2.3.1)
|
20
|
+
rspec-expectations (2.3.0)
|
21
|
+
diff-lcs (~> 1.1.2)
|
22
|
+
rspec-mocks (2.3.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
activesupport (~> 2.3.5)
|
29
|
+
bundler (~> 1.0.0)
|
30
|
+
curb (~> 0.7.10)
|
31
|
+
jeweler (~> 1.5.2)
|
32
|
+
json (~> 1.5.1)
|
33
|
+
rcov
|
34
|
+
rspec (~> 2.3.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Travel IQ
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
= tapi - Client for the Travel IQ API
|
2
|
+
|
3
|
+
This is the reference client implementation in Ruby for the Travel IQ API.
|
4
|
+
|
5
|
+
The API is a RESTful webservice which enables you to build meta travel search services or programs.
|
6
|
+
|
7
|
+
For the full documentation, visit http://apiv3.travel-iq.com/
|
8
|
+
|
9
|
+
To access the API, you need an API key - you can get that by signing up for the Travel IQ affiliate program at http://www.travel-iq.com/partners .
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
The TAPI client provides both easy HTTP request handling (with built-in, transparent HTTP etag handling), and a Parser that
|
14
|
+
returns response data as dynamic documents that allow you to access the data and do subsequent requests with ease.
|
15
|
+
|
16
|
+
This is best illustrated with an example. Here's how you autocomplete a location, start a hotel search, and query the results, using the tapi.
|
17
|
+
|
18
|
+
Load up tapi in an irb session:
|
19
|
+
|
20
|
+
$ irb
|
21
|
+
=> require 'rubygems'
|
22
|
+
=> gem 'tapi'
|
23
|
+
=> require 'tapi'
|
24
|
+
|
25
|
+
Get the first city matching the name "Berlin":
|
26
|
+
|
27
|
+
=> city = TAPI::V3::Client.new_from_get("http://apiv3.travel-iq.com/locations/cities/name/Berlin.json?key=traveliq").first.cities.first
|
28
|
+
|
29
|
+
The client outputs what it's doing:
|
30
|
+
|
31
|
+
>> TAPI GET 0.221213 http://apiv3.travel-iq.com/locations/cities/name/Berlin.json?key=traveliq {} "504f7ed4c2170bac4a8537eea29aa7b8"
|
32
|
+
>> Unknown ETag.
|
33
|
+
|
34
|
+
And this is the result:
|
35
|
+
|
36
|
+
>> #<TAPI::V3::Client:0x1589a48
|
37
|
+
@document=
|
38
|
+
{:distance=>nil,
|
39
|
+
:latitude=>52.5166667,
|
40
|
+
:country=>"Deutschland",
|
41
|
+
:country_code=>"DE",
|
42
|
+
:population=>3383782,
|
43
|
+
:longitude=>13.4,
|
44
|
+
:resources=>
|
45
|
+
#<TAPI::V3::Client:0x1588e90
|
46
|
+
@document=
|
47
|
+
{:hotels_url=>
|
48
|
+
"http://apiv3.travel-iq.com/locations/cities/40784/hotels.json",
|
49
|
+
:city_url=>"http://apiv3.travel-iq.com/locations/cities/40784.json",
|
50
|
+
:airports_url=>
|
51
|
+
"http://apiv3.travel-iq.com/locations/cities/40784/airports.json",
|
52
|
+
:country_url=>"http://apiv3.travel-iq.com/locations/countries/10.json",
|
53
|
+
:description_url=>
|
54
|
+
"http://apiv3.travel-iq.com/locations/cities/40784/description.json"}>,
|
55
|
+
:name=>"Berlin",
|
56
|
+
:id=>40784,
|
57
|
+
:display_name=>"Berlin - Deutschland"}>
|
58
|
+
|
59
|
+
Since all results are again instances of TAPI::V3::Client, you can both get the data with accessor methods:
|
60
|
+
|
61
|
+
>> city.name
|
62
|
+
=> "Berlin"
|
63
|
+
>> city.latitude
|
64
|
+
=> 52.5166667
|
65
|
+
|
66
|
+
And query further parts of the API, which are always linked by the "resources" key in the API responses, by prepending "fetch_" to the resource name:
|
67
|
+
|
68
|
+
>> city.fetch_country
|
69
|
+
=> #<TAPI::V3::Client:0x2137048
|
70
|
+
@document=
|
71
|
+
{:country=>
|
72
|
+
#<TAPI::V3::Client:0x21367b0
|
73
|
+
@document=
|
74
|
+
{:currency=>"EUR",
|
75
|
+
:code=>"DE",
|
76
|
+
:population=>83536115,
|
77
|
+
:recources=>
|
78
|
+
#<TAPI::V3::Client:0x2136648
|
79
|
+
@document=
|
80
|
+
{:country_url=>
|
81
|
+
"http://apiv3.travel-iq.com/locations/countries/10.json",
|
82
|
+
:cities_url=>
|
83
|
+
"http://apiv3.travel-iq.com/locations/countries/10/cities.json"}>,
|
84
|
+
:name=>"Deutschland",
|
85
|
+
:id=>10}>},
|
86
|
+
@etag="\"3a762ad93a79192f474407a1d0a1f059\"">
|
87
|
+
|
88
|
+
To start a search, you first need to configure the Search class, and set up the parameters in an instance:
|
89
|
+
|
90
|
+
>> TAPI::V3::Hotels::Search.config = {:key => "traveliq", :host => "apiv3.travel-iq.com", :port => "80", :path => ''}
|
91
|
+
>> search = TAPI::V3::Hotels::Search.new({:arrival_date => Date.today + 1, :departure_date => Date.today + 2, :city_id => city.id, :room_configuration => "[A]"})
|
92
|
+
|
93
|
+
You can validate the parameters like in ActiveRecord with #valid?
|
94
|
+
When everything's set up, start the search:
|
95
|
+
|
96
|
+
>> search.start!
|
97
|
+
|
98
|
+
The search will take a little while to finish. You can query its status like this (note the reload, which does a new request to the API):
|
99
|
+
|
100
|
+
>> search.reload.status_detailed.state
|
101
|
+
=> "running"
|
102
|
+
|
103
|
+
Later:
|
104
|
+
|
105
|
+
>> search.reload.status_detailed.state
|
106
|
+
=> "finished"
|
107
|
+
|
108
|
+
You can look at the results of a search at any time. Keep in mind, though, that results will expire after a while - display results while the search is running or when finished,
|
109
|
+
don't cache them in your application.
|
110
|
+
|
111
|
+
>> search.fetch_results.search.results.first.price
|
112
|
+
=> 24.0
|
113
|
+
|
114
|
+
|
115
|
+
== Installation
|
116
|
+
|
117
|
+
(sudo) gem install tapi
|
118
|
+
|
119
|
+
Or, install latest version from Github with bundler by adding this to your Gemfile:
|
120
|
+
|
121
|
+
gem 'tapi', :git => 'git://github.com/traveliq/tapi.git'
|
122
|
+
|
123
|
+
== Dependencies and Rubies
|
124
|
+
|
125
|
+
You'll need the ActiveSupport, curl, and json gems, but that should be handled automatically.
|
126
|
+
The client is tested under Ruby 1.8.7 (REE). It may not work well under 1.9 - feel free to contribute !
|
127
|
+
|
128
|
+
== Authors
|
129
|
+
|
130
|
+
Dr. Florian Odronitz (odo@mac.com)
|
131
|
+
Matthias Georgi (http://www.matthias-georgi.de)
|
132
|
+
Github release by Martin Tepper (monogreen.de)
|
133
|
+
|
134
|
+
== Contact
|
135
|
+
|
136
|
+
For questions, contact the authors or developer@traveliq.net
|
137
|
+
|
138
|
+
== Contributing to tapi
|
139
|
+
|
140
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
141
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
142
|
+
* Fork the project
|
143
|
+
* Start a feature/bugfix branch
|
144
|
+
* Commit and push until you are happy with your contribution
|
145
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
146
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
147
|
+
|
148
|
+
== Copyright
|
149
|
+
|
150
|
+
Copyright (c) 2011 Travel IQ. See LICENSE.txt for
|
151
|
+
further details.
|
152
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "tapi"
|
16
|
+
gem.homepage = "http://github.com/traveliq/tapi"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Reference client to the Travel IQ API at http://apiv3.travel-iq.com/}
|
19
|
+
gem.description = gem.summary
|
20
|
+
gem.email = "developer@traveliq.net"
|
21
|
+
gem.authors = ["Martin Tepper"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "tapi #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/lib/tapi.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
puts require 'active_support'
|
2
|
+
require 'tapi/v3/configurable'
|
3
|
+
require 'tapi/v3/errors'
|
4
|
+
require 'tapi/v3/client'
|
5
|
+
require 'tapi/v3/validations'
|
6
|
+
require 'tapi/v3/utils'
|
7
|
+
require 'tapi/v3/generic_search'
|
8
|
+
require 'tapi/v3/hotels/search'
|
9
|
+
require 'tapi/v3/flights/search'
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TAPI
|
3
|
+
module V3
|
4
|
+
class Client
|
5
|
+
include Configurable
|
6
|
+
|
7
|
+
require 'uri'
|
8
|
+
require 'digest'
|
9
|
+
require 'curl'
|
10
|
+
require 'json'
|
11
|
+
require 'logger'
|
12
|
+
|
13
|
+
attr_reader :etag, :document
|
14
|
+
|
15
|
+
HTTP_ERRORS = {
|
16
|
+
302 => MovedError,
|
17
|
+
205 => ExpiredError,
|
18
|
+
401 => InternalServerError,
|
19
|
+
404 => NotFoundError,
|
20
|
+
500 => InternalServerError
|
21
|
+
}
|
22
|
+
|
23
|
+
class << self
|
24
|
+
|
25
|
+
def http_authentication
|
26
|
+
self.class.http_authentication
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_from_post(url, params)
|
30
|
+
curl, server_etag = execute_request(:post, url, params)
|
31
|
+
new(JSON.parse(curl.body_str), nil, true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_from_get(url, options = {}, etag = nil)
|
35
|
+
klass = options.delete(:instanciate_as) || self
|
36
|
+
curl, server_etag = execute_request(:get, url, options, etag)
|
37
|
+
|
38
|
+
if server_etag && etag == server_etag
|
39
|
+
logger.debug "Known ETag."
|
40
|
+
[nil, server_etag]
|
41
|
+
else
|
42
|
+
logger.debug "Unknown ETag."
|
43
|
+
[klass.new(JSON.parse(curl.body_str), server_etag, true), server_etag]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_for_errors(curl)
|
48
|
+
error_class = HTTP_ERRORS[curl.response_code]
|
49
|
+
error_class ||= Error if (401..599).include?(curl.response_code)
|
50
|
+
|
51
|
+
if error_class
|
52
|
+
error = error_class.new
|
53
|
+
error.request_url = curl.url
|
54
|
+
error.response_code = curl.response_code
|
55
|
+
error.response_body = curl.body_str
|
56
|
+
raise error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_etag(str)
|
61
|
+
str.split("\r\n").grep(/^etag/i).first.split(' ').last if str =~ /^etag/i
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute_request(method, url, params, etag = nil)
|
65
|
+
url = base_url + url
|
66
|
+
url = Utils.append_query(url, params) if Hash === params and method == :get
|
67
|
+
|
68
|
+
curl = Curl::Easy.new(url)
|
69
|
+
|
70
|
+
if auth = http_authentication
|
71
|
+
curl.userpwd = auth
|
72
|
+
end
|
73
|
+
|
74
|
+
curl.headers["If-None-Match"] = etag if etag
|
75
|
+
|
76
|
+
server_etag = nil
|
77
|
+
|
78
|
+
time = Time.now
|
79
|
+
case method
|
80
|
+
when :get
|
81
|
+
curl.http_get
|
82
|
+
server_etag = parse_etag(curl.header_str)
|
83
|
+
|
84
|
+
when :post
|
85
|
+
fields = params.to_a.map {|key, value| Curl::PostField.content(key.to_s, value.to_s)}
|
86
|
+
|
87
|
+
curl.http_post(fields)
|
88
|
+
end
|
89
|
+
|
90
|
+
logger.debug "TAPI #{method.to_s.upcase} #{Time.now - time} #{url} #{params.inspect} #{etag} #{server_etag}"
|
91
|
+
|
92
|
+
check_for_errors(curl)
|
93
|
+
|
94
|
+
[curl, server_etag]
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def logger
|
99
|
+
Thread.current[:tapi_logger] || Logger.new(STDOUT)
|
100
|
+
end
|
101
|
+
|
102
|
+
def logger=(logger)
|
103
|
+
Thread.current[:tapi_logger] = logger
|
104
|
+
end
|
105
|
+
|
106
|
+
def base_url
|
107
|
+
config[:base_url] || ""
|
108
|
+
end
|
109
|
+
|
110
|
+
def http_authentication
|
111
|
+
if config[:http_password] && config[:http_user_name]
|
112
|
+
"#{config[:http_user_name]}:#{config[:http_password]}"
|
113
|
+
else
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
end # end class methods
|
120
|
+
|
121
|
+
def initialize(hash, etag = nil, is_root = false)
|
122
|
+
@document = {}
|
123
|
+
@etag = etag if etag
|
124
|
+
|
125
|
+
update(hash)
|
126
|
+
end
|
127
|
+
|
128
|
+
def logger
|
129
|
+
self.class.logger
|
130
|
+
end
|
131
|
+
|
132
|
+
undef id if instance_methods.include?('id')
|
133
|
+
|
134
|
+
def to_hash
|
135
|
+
@document.inject({}) do |hash, (key, val)|
|
136
|
+
hash[key] = \
|
137
|
+
case val
|
138
|
+
when Client then val.to_hash
|
139
|
+
when Array then val.map {|v| Client === v ? v.to_hash : v }
|
140
|
+
else val
|
141
|
+
end
|
142
|
+
hash
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_json
|
147
|
+
to_hash.to_json
|
148
|
+
end
|
149
|
+
|
150
|
+
def attributes
|
151
|
+
@document.keys.map(&:to_s)
|
152
|
+
end
|
153
|
+
|
154
|
+
def urls
|
155
|
+
if search = @document[:search]
|
156
|
+
search.urls
|
157
|
+
elsif @document[:resources]
|
158
|
+
@document[:resources].to_hash
|
159
|
+
else
|
160
|
+
Hash.new
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def remote_calls
|
165
|
+
urls.keys.map{|resource| "fetch_#{/(.*)_url$/.match(resource.to_s)[1]}"}.sort
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_param
|
169
|
+
id
|
170
|
+
end
|
171
|
+
|
172
|
+
def respond_to?(key, include_private = false)
|
173
|
+
@document.has_key?(key) or
|
174
|
+
urls["#{url_key(key)}_url".to_sym] or
|
175
|
+
super(key, include_private)
|
176
|
+
end
|
177
|
+
|
178
|
+
def class_mapping
|
179
|
+
config[:class_mapping] ||= {}
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def update(hash)
|
185
|
+
hash.each do |key, value|
|
186
|
+
@document[key.to_sym] = \
|
187
|
+
case value
|
188
|
+
when Hash
|
189
|
+
client_class(key).new(value)
|
190
|
+
when Array
|
191
|
+
klass = client_class(key)
|
192
|
+
value.map { |e| Hash === e ? klass.new(e) : e }
|
193
|
+
when String
|
194
|
+
if value.respond_to?(:force_encoding)
|
195
|
+
value.force_encoding(Encoding::UTF_8)
|
196
|
+
else
|
197
|
+
value
|
198
|
+
end
|
199
|
+
else
|
200
|
+
value
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def method_missing(key, *args)
|
206
|
+
if @document.has_key?(key)
|
207
|
+
@document[key]
|
208
|
+
else
|
209
|
+
if url = urls["#{url_key(key)}_url".to_sym]
|
210
|
+
options = args.first || {}
|
211
|
+
get(url, client_class(key), options)
|
212
|
+
else
|
213
|
+
raise NoMethodError, "undefined method `#{key}' for #{self.class}", caller
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def url_key(key)
|
219
|
+
(match = /^fetch_(.*)/.match(key.to_s)) && match[1]
|
220
|
+
end
|
221
|
+
|
222
|
+
def remote_cache
|
223
|
+
@remote_cache ||= {}
|
224
|
+
end
|
225
|
+
|
226
|
+
def get(url, klass, options = {})
|
227
|
+
cache_key = cache_key(url, options)
|
228
|
+
cached_reply = remote_cache[cache_key]
|
229
|
+
|
230
|
+
return cached_reply[:data] if options.delete(:skip_refresh) and cached_reply
|
231
|
+
old_etag = cached_reply ? cached_reply[:etag] : nil
|
232
|
+
|
233
|
+
if options.delete(:skip_cache)
|
234
|
+
return self.class.new_from_get(url, options.merge(:instanciate_as => klass), nil).first
|
235
|
+
end
|
236
|
+
|
237
|
+
data, etag = self.class.new_from_get(url, options.merge(:instanciate_as => klass), old_etag)
|
238
|
+
|
239
|
+
if etag && etag == old_etag
|
240
|
+
logger.debug "ETag match. Returning data from internal cache."
|
241
|
+
remote_cache[cache_key][:data]
|
242
|
+
else
|
243
|
+
logger.debug "Returning fetched data."
|
244
|
+
if etag
|
245
|
+
remote_cache[cache_key] = {:data => data, :etag => etag}
|
246
|
+
end
|
247
|
+
data
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def cache_key(url, options)
|
252
|
+
Digest::MD5.hexdigest(url.to_s + options.to_a.sort {|a, b| a.first.to_s <=> b.first.to_s }.flatten.join)
|
253
|
+
end
|
254
|
+
|
255
|
+
def client_class(name)
|
256
|
+
class_mapping[name.to_sym] || Client
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|