roverjoe 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ .rvmrc
2
+ .bundle
3
+ .rspec
4
+ tmp/*
5
+ .DS_Store
6
+ tags
7
+ Session.vim
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.3@roverjoe
data/Gemfile ADDED
@@ -0,0 +1,24 @@
1
+ source :rubygems
2
+
3
+ gem 'addressable'
4
+ gem 'httparty'
5
+
6
+ group :development do
7
+ gem 'rake'
8
+ end
9
+
10
+ group :test do
11
+ gem 'rspec'
12
+ gem 'guard'
13
+ gem 'guard-rspec'
14
+ gem 'guard-cucumber'
15
+ gem 'webmock'
16
+ end
17
+
18
+ group :cucumber do
19
+ gem 'cucumber'
20
+ gem 'rspec'
21
+ gem 'guard'
22
+ gem 'guard-rspec'
23
+ gem 'guard-cucumber'
24
+ end
@@ -0,0 +1,65 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.8)
5
+ builder (3.0.0)
6
+ crack (0.3.1)
7
+ cucumber (1.2.1)
8
+ builder (>= 2.1.2)
9
+ diff-lcs (>= 1.1.3)
10
+ gherkin (~> 2.11.0)
11
+ json (>= 1.4.6)
12
+ diff-lcs (1.1.3)
13
+ ffi (1.0.11)
14
+ gherkin (2.11.0)
15
+ json (>= 1.4.6)
16
+ guard (1.1.1)
17
+ listen (>= 0.4.2)
18
+ thor (>= 0.14.6)
19
+ guard-cucumber (1.1.0)
20
+ cucumber (>= 1.2.0)
21
+ guard (>= 1.1.0)
22
+ guard-rspec (1.1.0)
23
+ guard (>= 1.1)
24
+ httparty (0.8.3)
25
+ multi_json (~> 1.0)
26
+ multi_xml
27
+ json (1.7.3)
28
+ listen (0.4.5)
29
+ rb-fchange (~> 0.0.5)
30
+ rb-fsevent (~> 0.9.1)
31
+ rb-inotify (~> 0.8.8)
32
+ multi_json (1.3.6)
33
+ multi_xml (0.5.1)
34
+ rake (0.9.2.2)
35
+ rb-fchange (0.0.5)
36
+ ffi
37
+ rb-fsevent (0.9.1)
38
+ rb-inotify (0.8.8)
39
+ ffi (>= 0.5.0)
40
+ rspec (2.10.0)
41
+ rspec-core (~> 2.10.0)
42
+ rspec-expectations (~> 2.10.0)
43
+ rspec-mocks (~> 2.10.0)
44
+ rspec-core (2.10.1)
45
+ rspec-expectations (2.10.0)
46
+ diff-lcs (~> 1.1.3)
47
+ rspec-mocks (2.10.1)
48
+ thor (0.15.3)
49
+ webmock (1.8.7)
50
+ addressable (>= 2.2.7)
51
+ crack (>= 0.1.7)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ addressable
58
+ cucumber
59
+ guard
60
+ guard-cucumber
61
+ guard-rspec
62
+ httparty
63
+ rake
64
+ rspec
65
+ webmock
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2, :cli => '--color --format nested' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
10
+ guard 'cucumber' do
11
+ watch(%r{^features/.+\.feature$})
12
+ watch(%r{^features/support/.+$}) { 'features' }
13
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
14
+ end
@@ -0,0 +1,102 @@
1
+ # RoverJoe
2
+
3
+ ![RoverJoe](http://images.wikia.com/puppet/images/3/35/Muppetbremen6.gif)
4
+
5
+ RoverJoe: A ruby gem that wraps the HostelWorld API, providing:
6
+
7
+ * Recovery from network issuses like timeout and connection reset by
8
+ peer.
9
+ * Recovery from quota exceeded conditions
10
+ * Request and Response objects
11
+ * Objects for RemoteProperty (work-in-progress) ...
12
+
13
+
14
+ ## Installation
15
+
16
+ Assuming bundler, edit Gemfile to include
17
+
18
+ gem 'roverjoe', '>=0.0.7', git: 'git@github.com:lonelyplanet/roverjoe.git'
19
+
20
+ ## Use
21
+
22
+ ### Configuration by file
23
+
24
+ Include in your initialization
25
+
26
+ RoverJoe.configure_from_file
27
+
28
+ Then create config/hostelworld\_api.yml something like this.
29
+
30
+ params:
31
+ format: 'json'
32
+ consumer_key: 'lonelyplanet.com'
33
+ consumer_signature: 'somethingverysecret'
34
+ Language: 'English'
35
+ uri: 'https://affiliate.xsapi.webresint.com/1.1/'
36
+
37
+ Only consumer\_key and consumer\_signature are required, the rest have
38
+ defaults.
39
+
40
+ ### Hardcoded configuration
41
+
42
+ RoverJoe.configure do |c|
43
+ c.consumer_key = 'lonelyplanet.com'
44
+ c.consumer_signature = 'asecretsignature'
45
+ c.logger = MyApp.logger
46
+ end
47
+
48
+ The default logger does nothing, override if you want to see log output.
49
+
50
+ ### Logging quota delay stats
51
+
52
+ Use the after\_delay callback to capture the stats however you want.
53
+
54
+ RoverJoe.configure do |c|
55
+ c.after_delay { |delay| Stats.capture( "the delay was #{delay}" }
56
+ end
57
+
58
+ ### Calling the API - RemoteProperty
59
+
60
+ rp = RoverJoe::RemoteProperty( :id => hostel_number )
61
+ star_rating = rp.star_rating # this triggers API call
62
+
63
+ or
64
+
65
+ hw_property = RoverJoe::RemoteProperty( :id => hostel_number ).response
66
+ lp_property.name = hw_property['PropertyName']
67
+
68
+ ### Calling the API - Request
69
+
70
+ req = RoverJoe::Request.new( :propertyInformation, :propertyNumber => hostel_number )
71
+ result = req.execute
72
+
73
+ Result is a hash of all the attributes of the property
74
+
75
+ ### Calling the API - Availability/Location search example
76
+
77
+ a = RoverJoe::Request.new( :propertylocationsearch,
78
+ :City => 'Athens',
79
+ :DateStart => (Date.today+21).iso8601,
80
+ :NumNights => 7
81
+ ).execute
82
+
83
+ ## Development
84
+
85
+ $ git clone git@github.com:lonelyplanet/roverjoe.git
86
+ $ cd roverjoe
87
+
88
+ ### RVM
89
+
90
+ $ cp .rvmrc.example .rvmrc
91
+ $ source .rvmrc
92
+ $ bundle install
93
+
94
+ ### Specs
95
+
96
+ $ rake # all tests
97
+ $ rspec # all tests
98
+ $ bundle exec guard # all tests
99
+
100
+ $ rspec spec/lib # unit tests
101
+ $ rspec spec/integration # integration tests
102
+
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,51 @@
1
+ require 'fileutils'
2
+
3
+ require_relative "roverjoe/remote_property"
4
+ require_relative "roverjoe/remote_properties_page"
5
+ require_relative "roverjoe/request"
6
+ require_relative "roverjoe/response"
7
+ require_relative "roverjoe/quota_handler"
8
+ require_relative "roverjoe/retry_handler"
9
+ require_relative "roverjoe/configuration"
10
+ require_relative "roverjoe/null_logger"
11
+
12
+
13
+ module RoverJoe
14
+
15
+ class PropertyInactiveError < RuntimeError ; end
16
+ class ExcessiveRetries < RuntimeError ; end
17
+
18
+ def self.root
19
+ FileUtils.pwd
20
+ end
21
+
22
+ def self.logger
23
+ configuration.logger
24
+ end
25
+
26
+ def self.configure_from_file( file = config_file )
27
+ reset_configuration
28
+ y = YAML.load_file( file )
29
+ configure do |c|
30
+ c.request_params = y['params']
31
+ c.request_uri = y['uri']
32
+ end
33
+ end
34
+
35
+ def self.config_file
36
+ File.join(RoverJoe.root, 'config', 'hostelworld_api.yml')
37
+ end
38
+
39
+ def self.reset_configuration
40
+ @configuration = nil
41
+ end
42
+
43
+ def self.configuration
44
+ @configuration ||= Configuration.new
45
+ end
46
+
47
+ def self.configure
48
+ yield configuration
49
+ end
50
+
51
+ end
@@ -0,0 +1,51 @@
1
+ module RoverJoe
2
+
3
+ class Configuration
4
+
5
+ attr_writer :logger, :request_uri, :consumer_signature, :consumer_key
6
+ attr_accessor :after_delay_callback
7
+
8
+ def logger
9
+ @logger ||= NullLogger.new
10
+ end
11
+
12
+ # HACK: If XML format is required, it may involve more changes to the gem
13
+ # than simply overriding the request format from JSON to XML.
14
+ def default_request_params
15
+ {
16
+ 'consumer_signature' => consumer_signature,
17
+ 'consumer_key' => consumer_key,
18
+ 'format' => 'json',
19
+ 'Language' => 'English'
20
+ }
21
+ end
22
+
23
+ def request_params
24
+ default_request_params.merge(@request_params ||= {})
25
+ end
26
+
27
+ def request_params=( params )
28
+ @request_params = params
29
+ self.consumer_key = params['consumer_key'] if params['consumer_key']
30
+ self.consumer_signature = params['consumer_signature'] if params['consumer_signature']
31
+ end
32
+
33
+ def request_uri
34
+ @request_uri ||= 'https://affiliate.xsapi.webresint.com/1.1/'
35
+ end
36
+
37
+ def consumer_signature
38
+ @consumer_signature ||= 'you-forgot-to-configure-consumer-signature'
39
+ end
40
+
41
+ def consumer_key
42
+ @consumer_key ||= 'you-forgot-to-configure-consumer-key'
43
+ end
44
+
45
+ def after_delay( &callback )
46
+ self.after_delay_callback = callback if block_given?
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,11 @@
1
+ module RoverJoe
2
+
3
+ class NullLogger
4
+
5
+ def method_missing( *args )
6
+ self
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,64 @@
1
+ module RoverJoe
2
+
3
+ require 'httparty'
4
+ require 'addressable/uri'
5
+
6
+
7
+ class QuotaHandler
8
+
9
+ attr_accessor :waiting_last_time, :quota_delay_start
10
+
11
+ def initialize
12
+ self.waiting_last_time = false
13
+ end
14
+
15
+ # HACK: The original intent was to limit requests to the
16
+ # hostelworld API, spreading them evenly over the quota window (60
17
+ # secs) thus being nice to other callers of the API with the same
18
+ # key. However, it's thought at present that there will only be
19
+ # one caller for any given API call, and so the actual throttling
20
+ # is not necessary and not implemented. Thus, all this method does
21
+ # is allow the calling code to hammer the API until it signals
22
+ # 'quota exceeded' after which it will keep retrying until the API
23
+ # becomes available again.
24
+ #
25
+ # If we do implement limiting or throttling it may belong in a
26
+ # different class.
27
+ def run
28
+ response = yield
29
+
30
+ retry_count = 0
31
+ while response.quota_exceeded? && retry_count < 200
32
+
33
+ log_quota_wait( true )
34
+ retry_count += 1
35
+ sleep 2
36
+ response = yield
37
+
38
+ end
39
+ log_quota_wait( response.quota_exceeded? )
40
+ raise RuntimeError, "Hostelworld API: Excessive wait for new quota window to start" if response.quota_exceeded?
41
+
42
+ response
43
+ end
44
+
45
+ private
46
+
47
+ def log_quota_wait( waiting_now )
48
+ if waiting_now != waiting_last_time
49
+ if waiting_now
50
+ self.quota_delay_start = Time.now
51
+ RoverJoe.logger.warn( "Hostelworld API: Quota exceeded delay begins" )
52
+ else
53
+ delay = Time.now - quota_delay_start
54
+ RoverJoe.logger.warn( "Hostelworld API: Quota exceeded delay ends. Total time: #{delay}" )
55
+ cb = RoverJoe.configuration.after_delay_callback
56
+ cb.call( delay ) unless cb.nil?
57
+ end
58
+ end
59
+ self.waiting_last_time = waiting_now
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,45 @@
1
+ module RoverJoe
2
+
3
+ class RemotePropertiesPage
4
+
5
+ attr_accessor :api_request_class, :page_number
6
+
7
+ def initialize( params )
8
+ @api_request_class = params.delete( :api_request_class ) || RoverJoe::Request
9
+ @page_number = params.delete( :page )
10
+ raise ArgumentError, ":page should not be blank" if @page_number.nil? or @page_number == ''
11
+ end
12
+
13
+ def response
14
+ @response ||= request.execute
15
+ end
16
+
17
+ def request
18
+ @request ||= api_request_class.new( :propertiesinformation, :PageNumber => self.page_number )
19
+ end
20
+
21
+ def self.all(&block)
22
+ page_1 = new(:page => 1)
23
+ block.call(page_1)
24
+ page_count = page_1.total_pages
25
+ if page_count > 1
26
+ (2..page_count).each do |n|
27
+ page = new(:page => n)
28
+ block.call(page)
29
+ end
30
+ end
31
+ end
32
+
33
+ def total_pages
34
+ raise RuntimeError, "'PagesCount' not found in #{response.keys} for RemotePropertiesPage #{page_number}." unless response.keys.include?( 'PagesCount' )
35
+ response['PagesCount']
36
+ end
37
+
38
+ def properties
39
+ raise RuntimeError, "'Properties' not found in #{response.keys} for RemotePropertiesPage #{page_number}." unless response.keys.include?( 'Properties' )
40
+ response['Properties']
41
+ end
42
+
43
+ end
44
+
45
+ end