roverjoe 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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