quicktravel_client 1.0.2

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.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +13 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +18 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +17 -0
  10. data/Rakefile +18 -0
  11. data/bootstrap/boot.rb +4 -0
  12. data/examples/example_config.rb +4 -0
  13. data/examples/login_example.rb +25 -0
  14. data/lib/extensions.rb +12 -0
  15. data/lib/quick_travel/accommodation.rb +72 -0
  16. data/lib/quick_travel/adapter.rb +283 -0
  17. data/lib/quick_travel/adapter_exception.rb +20 -0
  18. data/lib/quick_travel/address.rb +27 -0
  19. data/lib/quick_travel/adjustment.rb +8 -0
  20. data/lib/quick_travel/background_check.rb +9 -0
  21. data/lib/quick_travel/bed_configuration.rb +7 -0
  22. data/lib/quick_travel/bed_requirement.rb +7 -0
  23. data/lib/quick_travel/booking.rb +370 -0
  24. data/lib/quick_travel/cache.rb +38 -0
  25. data/lib/quick_travel/client.rb +8 -0
  26. data/lib/quick_travel/client_type.rb +6 -0
  27. data/lib/quick_travel/config.rb +26 -0
  28. data/lib/quick_travel/connection_error.rb +9 -0
  29. data/lib/quick_travel/constants.rb +16 -0
  30. data/lib/quick_travel/contact.rb +5 -0
  31. data/lib/quick_travel/country.rb +11 -0
  32. data/lib/quick_travel/credit_card.rb +12 -0
  33. data/lib/quick_travel/discounts/booking_discount.rb +18 -0
  34. data/lib/quick_travel/discounts/discount.rb +21 -0
  35. data/lib/quick_travel/discounts/discount_tree.rb +35 -0
  36. data/lib/quick_travel/discounts.rb +3 -0
  37. data/lib/quick_travel/document.rb +20 -0
  38. data/lib/quick_travel/document_group.rb +14 -0
  39. data/lib/quick_travel/encrypt.rb +13 -0
  40. data/lib/quick_travel/graphic.rb +18 -0
  41. data/lib/quick_travel/init_from_hash.rb +16 -0
  42. data/lib/quick_travel/location.rb +14 -0
  43. data/lib/quick_travel/mock.rb +41 -0
  44. data/lib/quick_travel/party.rb +49 -0
  45. data/lib/quick_travel/passenger.rb +7 -0
  46. data/lib/quick_travel/passenger_price_break.rb +8 -0
  47. data/lib/quick_travel/passenger_type.rb +21 -0
  48. data/lib/quick_travel/payment.rb +41 -0
  49. data/lib/quick_travel/payment_type.rb +37 -0
  50. data/lib/quick_travel/price.rb +14 -0
  51. data/lib/quick_travel/product.rb +165 -0
  52. data/lib/quick_travel/product_configuration.rb +134 -0
  53. data/lib/quick_travel/product_passenger_search_criteria.rb +28 -0
  54. data/lib/quick_travel/product_type.rb +28 -0
  55. data/lib/quick_travel/property.rb +99 -0
  56. data/lib/quick_travel/property_facility.rb +7 -0
  57. data/lib/quick_travel/property_type.rb +20 -0
  58. data/lib/quick_travel/region.rb +21 -0
  59. data/lib/quick_travel/reservation.rb +110 -0
  60. data/lib/quick_travel/resource.rb +50 -0
  61. data/lib/quick_travel/room_facility.rb +7 -0
  62. data/lib/quick_travel/route.rb +81 -0
  63. data/lib/quick_travel/route_stop.rb +7 -0
  64. data/lib/quick_travel/search.rb +14 -0
  65. data/lib/quick_travel/service.rb +5 -0
  66. data/lib/quick_travel/trip.rb +8 -0
  67. data/lib/quick_travel/vehicle.rb +17 -0
  68. data/lib/quick_travel/vehicle_type.rb +11 -0
  69. data/lib/quick_travel/version.rb +3 -0
  70. data/lib/quick_travel.rb +59 -0
  71. data/lib/quicktravel_client.rb +1 -0
  72. data/quicktravel_client.gemspec +37 -0
  73. data/spec/booking_spec.rb +107 -0
  74. data/spec/country_spec.rb +18 -0
  75. data/spec/credit_card_spec.rb +11 -0
  76. data/spec/discounts_spec.rb +121 -0
  77. data/spec/passenger_type_spec.rb +32 -0
  78. data/spec/payment_type_spec.rb +18 -0
  79. data/spec/product_spec.rb +82 -0
  80. data/spec/region_spec.rb +29 -0
  81. data/spec/reservation_spec.rb +50 -0
  82. data/spec/resource_spec.rb +22 -0
  83. data/spec/spec_helper.rb +77 -0
  84. data/spec/support/cassettes/booking_create.yml +50 -0
  85. data/spec/support/cassettes/booking_create_legacy.yml +50 -0
  86. data/spec/support/cassettes/booking_discounts.yml +53 -0
  87. data/spec/support/cassettes/booking_documents.yml +48 -0
  88. data/spec/support/cassettes/booking_show.yml +49 -0
  89. data/spec/support/cassettes/booking_update.yml +94 -0
  90. data/spec/support/cassettes/booking_with_discounts.yml +72 -0
  91. data/spec/support/cassettes/booking_with_documents.yml +117 -0
  92. data/spec/support/cassettes/booking_with_nested_attributes.yml +187 -0
  93. data/spec/support/cassettes/country_all.yml +127 -0
  94. data/spec/support/cassettes/create_reservation_fail.yml +47 -0
  95. data/spec/support/cassettes/create_reservation_with_booking.yml +53 -0
  96. data/spec/support/cassettes/passenger_all.yml +48 -0
  97. data/spec/support/cassettes/product_date_range_bookability.yml +118 -0
  98. data/spec/support/cassettes/product_show.yml +52 -0
  99. data/spec/support/cassettes/region_index.yml +48 -0
  100. data/spec/support/cassettes/region_show.yml +48 -0
  101. data/spec/support/cassettes/reservation_with_extra_picks.yml +165 -0
  102. data/spec/support/cassettes/resource_fare_bases.yml +97 -0
  103. data/spec/support/cassettes/resource_show.yml +50 -0
  104. metadata +421 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d115ad4405fbddb775a4e3c83e85e6e1577dfc6a
4
+ data.tar.gz: 422e5e9df4d27248109ede5ca9ef390398029fed
5
+ SHA512:
6
+ metadata.gz: f326cf1e9778cbb0b11523e1c6c89f1ce3b5febaba6d9ce61f90dc58b3e0371994ea8b2fd9e7f78a08d421ed91cf0c02edd066ce8631267f2beee00752510781
7
+ data.tar.gz: b5c0b87403273ded2efe88d85c2511f7fff40763b1b88c9b14a1dc5cc71905f96f4eaeee60c1973421f13c0fa1fa2a38ea9dad912c5aab548c598dcaee7a475f
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ pkg
2
+ coverage
3
+ Gemfile.lock
4
+
5
+ # Temp files
6
+ tmp
7
+ *.swp
8
+ *~
9
+ \#*
10
+ .\#*
11
+ *.orig
12
+ *.DS_Store
13
+
14
+ # Log files
15
+ /log
16
+ /debug.log
17
+
18
+ # Ide droppings
19
+ /.idea
20
+ /.eclipse
21
+ /nbproject
22
+ /.redcar
23
+ /tags
24
+ /.tags
25
+ /TAGS
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Documentation:
5
+ Enabled: false
6
+
7
+ Style/Blocks:
8
+ Enabled: false
9
+
10
+ Style/TrivialAccessors:
11
+ AllowPredicates: true
12
+ AllowDSLWriters: true
13
+ ExactNameMatch: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.5
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9
4
+ - 2.2
5
+ script: "bundle && bundle exec rake coverage"
6
+ gemfile:
7
+ - gemfiles/rails2.gemfile
8
+ - gemfiles/rails4.gemfile
9
+ matrix:
10
+ exclude:
11
+ - rvm: 1.9
12
+ gemfile: gemfiles/rails4.gemfile
13
+ - rvm: 2.2
14
+ gemfile: gemfiles/rails2.gemfile
15
+ notifications:
16
+ email:
17
+ - support@travellink.com.au
18
+ flowdock: e69dcafad1fea15c6b8c76e9ced965af
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ # TODO: Give money extensions proper versioning and release it
5
+ gem 'money_extensions', github: 'sealink/money_extensions', branch: 'master'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 SeaLink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ QuickTravel
2
+ ===========
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/quicktravel_client.svg)](http://badge.fury.io/rb/quicktravel_client)
5
+ [![Build Status](https://travis-ci.org/sealink/quicktravel_client.svg?branch=master)](https://travis-ci.org/sealink/quicktravel_client)
6
+ [![Coverage Status](https://coveralls.io/repos/sealink/quicktravel_client/badge.svg)](https://coveralls.io/r/sealink/quicktravel_client)
7
+ [![Dependency Status](https://gemnasium.com/sealink/quicktravel_client.svg)](https://gemnasium.com/sealink/quicktravel_client)
8
+ [![Code Climate](https://codeclimate.com/github/sealink/quicktravel_client/badges/gpa.svg)](https://codeclimate.com/github/sealink/quicktravel_client)
9
+
10
+ # DESCRIPTION
11
+
12
+ Gives models that are integrated with the QuickTravel API
13
+
14
+ # INSTALLATION
15
+
16
+ Add to your Gemfile:
17
+ gem 'quicktravel_client'
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Default: run specs.'
4
+ task :default => :spec
5
+
6
+ require 'rspec/core/rake_task'
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
12
+ end
13
+
14
+ desc "Generate SimpleCov test coverage and open in your browser"
15
+ task :coverage do
16
+ ENV['COVERAGE'] = 'true'
17
+ Rake::Task['spec'].invoke
18
+ end
data/bootstrap/boot.rb ADDED
@@ -0,0 +1,4 @@
1
+ QUICK_TRAVEL_ACCESS_KEY = '6b50117c69393703d27ff1c346da8d60a087a39ca9604cefd65f1133af4c2a48'
2
+ QUICK_TRAVEL_INTERNAL_URL = QUICK_TRAVEL_PUBLIC_URL = 'http://localhost:3000'
3
+
4
+ require 'quick_travel'
@@ -0,0 +1,4 @@
1
+ QuickTravel.configure do |config|
2
+ config.url = 'http://localhost:8080/'
3
+ config.access_key = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
4
+ end
@@ -0,0 +1,25 @@
1
+ # Add current dir to path
2
+ $LOAD_PATH.unshift File.dirname(__FILE__)
3
+
4
+ gem 'quicktravel_client'
5
+ require 'quicktravel_client'
6
+
7
+ require 'example_config'
8
+
9
+ puts 'Login with username:'
10
+ login = gets.chomp
11
+ puts 'And password:'
12
+ password = gets.chomp
13
+
14
+ puts "Ok #{login}, just a tick..."
15
+ result = begin
16
+ QuickTravel::Party.login(login: login, password: password)
17
+ rescue QuickTravel::AdapterException
18
+ nil
19
+ end
20
+
21
+ if result
22
+ puts "Ok. You'll do this time."
23
+ else
24
+ puts 'No go, chappy'
25
+ end
data/lib/extensions.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'facets/kernel/blank'
2
+
3
+ # Can not use. It inteferes with active support underscore
4
+ # which is used by auto load! Such fun!
5
+ #
6
+ # See
7
+ # * core/facets/string/underscore.rb
8
+ # * core/facets/string/snakecase.rb
9
+ #
10
+ # Note, this method no longer converts `::` to `/`, in that case
11
+ # use the {#pathize} method instead.
12
+ #require 'facets/string/underscore'
@@ -0,0 +1,72 @@
1
+ require 'quick_travel/adapter'
2
+ require 'quick_travel/bed_configuration'
3
+ require 'quick_travel/room_facility'
4
+
5
+ module QuickTravel
6
+ class Accommodation < Adapter
7
+ # TODO: find a dynamic way to provide getter/setters for all data members instead of writing all of them.
8
+
9
+ attr_accessor :book_before_level, :book_before_units, :bookable_individually, :bookable_online, :booking_notes, :code, :created_at
10
+ attr_accessor :days_before_inventory_expires, :default_capacity, :deposit_id, :disclaimer_id, :expiry_level, :expiry_units, :fare_basis_pointer_id
11
+ attr_accessor :frequent_traveller_points_multiplier, :guardian_minimum_age, :id, :inline_cost_in_cents, :inline_price_in_cents, :inline_pricing
12
+ attr_accessor :inventory_type, :active, :location_id, :masterpoint_resource_id, :maximum_occupancy, :maximum_passengers, :maximum_passengers_online
13
+ attr_accessor :maximum_weight, :minimum_age, :minimum_passengers, :name, :no_expiry, :non_commissionable, :on_request_after_inventory_expiration
14
+ attr_accessor :overriding_passenger_ticket_format_id, :overriding_reservation_ticket_format_id, :overriding_vehicle_ticket_format_id
15
+ attr_accessor :product_type_id, :property_id, :property_type_id, :reason_required, :report_changes, :required_number_of_vehicles
16
+ attr_accessor :resource_category_id, :restrict_to_client_types, :star_rating, :type, :unlimited_uses, :updated_at, :uses, :vendor_id
17
+ attr_accessor :availability, :minimum_bookable_duration, :description,
18
+ :minimum_price, :nightly_price
19
+ attr_accessor :on_request
20
+ attr_accessor :error
21
+
22
+ attr_reader :bed_configurations, :room_facilities
23
+
24
+ MAX_DAYS = 8
25
+
26
+ def bed_configurations=(hash_array)
27
+ @bed_configurations = hash_array.map { |bed_configuration_hash| BedConfiguration.new(bed_configuration_hash) }
28
+ end
29
+
30
+ def room_facilities=(hash_array)
31
+ @room_facilities = hash_array.map { |room_facility_hash| RoomFacility.new(room_facility_hash) }
32
+ end
33
+
34
+ # The minimum price may be in 'nightly' or 'whole-duration-totalled' form, depending on options given to API
35
+ #
36
+ # I use these accessors to set it up as is more sane for users of the class.
37
+ attr_accessor :minimum_nightly_price, :minimum_price_for_duration
38
+
39
+ def minimum_price_on(date)
40
+ return nil if minimum_price.blank? || minimum_price[date.to_s].blank?
41
+ Money.new(minimum_price[date.to_s])
42
+ end
43
+
44
+ def nightly_price_on(date)
45
+ return nil if nightly_price.blank? || nightly_price[date.to_s].blank?
46
+ Money.new(nightly_price[date.to_s])
47
+ end
48
+
49
+ def available_over_dates?(dates)
50
+ return false if dates.empty?
51
+ dates.all? { |date| available_on?(date.to_s) }
52
+ end
53
+
54
+ def available_on?(date)
55
+ availability[date.to_s]
56
+ end
57
+
58
+ # Returns resource -- use for full information
59
+ def full_information
60
+ return @full_resource if @full_resource.present?
61
+ @full_resource = Resource.first(@id) unless @id.blank?
62
+ end
63
+
64
+ def graphics=(graphics)
65
+ @graphic_attributes = graphics
66
+ end
67
+
68
+ def graphics
69
+ @graphics ||= @graphic_attributes.map { |g| QuickTravel::Graphic.new(g) }
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,283 @@
1
+ require 'httparty'
2
+ require 'pp'
3
+ require 'json'
4
+ require 'active_support/core_ext'
5
+ require 'money'
6
+ require 'money_extensions/money_field'
7
+
8
+ require 'quick_travel/config'
9
+ require 'quick_travel/adapter_exception'
10
+ require 'quick_travel/init_from_hash'
11
+
12
+ module QuickTravel
13
+ class Adapter
14
+ include HTTParty
15
+ include QuickTravel::InitFromHash
16
+ include MoneyField
17
+
18
+ class_attribute :api_base, :lookup
19
+
20
+ # In case of object_key_name, we need to extract each record by a key
21
+ #
22
+ # Example:
23
+ # [
24
+ # { :passenger_type => {"position"=>1, "name"=>"Adult", "maximum_age"=>30, "id"=>1} },
25
+ # { :passenger_type => {"position"=>2, "name"=>"Child", "maximum_age"=>15, "id"=>2} }
26
+ # ]
27
+ class_attribute :object_key # Key of sub-objects (i.e. convert under a key)
28
+
29
+ def initialize(hash = {})
30
+ return nil if hash.blank?
31
+ super(Parser.new(hash).parsed_attributes)
32
+ end
33
+
34
+ def self.find(id, opts = {})
35
+ if lookup
36
+ all.detect { |o| o.id == id.to_i }
37
+ else
38
+ find_all!("#{api_base}/#{id}.json", opts).first
39
+ end
40
+ end
41
+
42
+ def self.all(opts = {})
43
+ if lookup
44
+ QuickTravel::Cache.cache("#{name}.all") {
45
+ find_all!("#{api_base}.json", opts)
46
+ }
47
+ else
48
+ find_all!("#{api_base}.json", opts)
49
+ end
50
+ end
51
+
52
+ def self.create(options = {})
53
+ post_and_validate("#{api_base}.json", options)
54
+ end
55
+
56
+ def self.update(id, options = {})
57
+ put_and_validate("#{api_base}/#{id}.json", options)
58
+ end
59
+
60
+ def self.qt_date_format_conversion(str)
61
+ qt_date_format_conversion!(str)
62
+ rescue ArgumentError
63
+ end
64
+
65
+ def self.qt_date_format_conversion!(str)
66
+ Date.strptime(str, '%d/%m/%Y').strftime('%d-%m-%Y')
67
+ end
68
+
69
+ def to_hash
70
+ instance_values
71
+ end
72
+
73
+ protected
74
+
75
+ # Find first
76
+ def self.generic_first(request_path, opts = {})
77
+ find_all!(request_path, opts).first
78
+ end
79
+
80
+ def self.find_all!(request_path, opts = {})
81
+ response = get_and_validate(request_path, opts, return_response_object: true)
82
+
83
+ deserializer = Deserializer.new(response.parsed_response)
84
+ objects = Array.wrap(deserializer.extract_under_root(self))
85
+
86
+ if response.headers['pagination'].present?
87
+ pagination_headers = ::JSON.parse(response.headers['pagination'])
88
+ WillPaginate::Collection.create(pagination_headers['current_page'], pagination_headers['per_page'], pagination_headers['total_entries']) do |pager|
89
+ pager.replace(objects)
90
+ end
91
+ else
92
+ objects
93
+ end
94
+ end
95
+
96
+ class Deserializer
97
+ def initialize(data)
98
+ @data = data
99
+ end
100
+
101
+ # Extract the 'root' -- the data must be a direct collection of objects
102
+ #
103
+ # opts[:object_key_name] => inside each hash extract attributes under a subkey (per object)
104
+ def extract_under_root(klass, opts = {})
105
+ self.class.extract(@data, klass, opts)
106
+ end
107
+
108
+ # Extract under a specified key in the data
109
+ def extract(key, klass, opts = {})
110
+ collection_data = @data[key]
111
+ fail "No collection key [#{key}] found in data from API" if collection_data.blank?
112
+ self.class.extract(collection_data, klass, opts)
113
+ end
114
+
115
+ def self.extract(objects, klass, opts = {})
116
+ objects = Array.wrap(objects)
117
+ objects.map! { |item| item[opts[:object_key_name]] } if opts[:object_key_name]
118
+ objects.map! { |item| klass.new(item) }
119
+
120
+ # if opts[:page] && opts[:per_page]
121
+ # objects = WillPaginate::Collection.create(opts[:page], opts[:per_page], opts).replace(objects)
122
+ objects
123
+ end
124
+ end
125
+
126
+ # The above generic_first find_all and the above find
127
+ # all suck
128
+ #
129
+ # We should make it standard behaviour that objects
130
+ # instantiate on get...
131
+ #
132
+ # Also, we should combine the two methods (get and validate) automatically...
133
+ #
134
+ # -> I found one - we can set custom parser()
135
+ # but that won't have access to response code only the body
136
+ #
137
+ # Better idea:
138
+ # Just override get, put, post and delete?
139
+ # If you need the originals, alias as raw.get() ?
140
+ # Seems to solve both issues...
141
+
142
+ def get_and_validate(path, query = {}, opts = {})
143
+ self.class.get_and_validate(path, query, opts)
144
+ end
145
+
146
+ def post_and_validate(path, query = {}, opts = {})
147
+ self.class.post_and_validate(path, query, opts)
148
+ end
149
+
150
+ def put_and_validate(path, query = {}, opts = {})
151
+ self.class.put_and_validate(path, query, opts)
152
+ end
153
+
154
+ def delete_and_validate(path, query = {}, opts = {})
155
+ self.class.delete_and_validate(path, query, opts)
156
+ end
157
+
158
+ def self.get_and_validate(path, query = {}, opts = {})
159
+ call_and_validate(:get, path, query, opts)
160
+ end
161
+
162
+ def self.post_and_validate(path, query = {}, opts = {})
163
+ call_and_validate(:post, path, query, opts)
164
+ end
165
+
166
+ def self.put_and_validate(path, query = {}, opts = {})
167
+ call_and_validate(:put, path, query, opts)
168
+ end
169
+
170
+ def self.delete_and_validate(path, query = {}, opts = {})
171
+ call_and_validate(:delete, path, query, opts)
172
+ end
173
+
174
+ def self.call_and_validate(http_method, path, query = {}, opts = {})
175
+ http_params = opts.clone
176
+ return_response_object = http_params.delete(:return_response_object)
177
+ klass = self
178
+
179
+ # Set default token
180
+ http_params[:query] ||= query
181
+ http_params[:headers] ||= {}
182
+ http_params[:headers]['Content-length'] = '0' if http_params[:body].blank?
183
+ expect = http_params.delete(:expect)
184
+
185
+ # Use :body instead of :query for put/post.
186
+ #
187
+ # Causes webrick to give back error - -maybe other servers too.
188
+ http_params[:body] ||= {}
189
+ if [:put, :post].include?(http_method.to_sym)
190
+ http_params[:body].merge!(http_params.delete(:query))
191
+ end
192
+ http_params[:body][:access_key] = QuickTravel.config.access_key
193
+ http_params[:follow_redirects] = false
194
+
195
+ begin
196
+ response = klass.send(http_method, path, http_params)
197
+ rescue Errno::ECONNREFUSED
198
+ raise ConnectionError.new('Connection refused')
199
+ rescue SocketError
200
+ raise ConnectionError.new('Socket error')
201
+ rescue Timeout::Error
202
+ raise ConnectionError.new('Timeout error')
203
+ end
204
+
205
+ if expect && expect == :json && !response.is_a?(Hash)
206
+ fail AdapterException, <<-FAIL
207
+ Request expected to be json but failed. Debug information below:
208
+ http_method: #{http_method.inspect}
209
+ path: #{path.inspect}
210
+ http_params: #{http_params.inspect}
211
+ response object: #{response.inspect}
212
+ parsed_response: #{response.parsed_response.inspect}
213
+ FAIL
214
+ end
215
+
216
+ klass.validate!(response)
217
+
218
+ if return_response_object
219
+ response
220
+ else
221
+ response.parsed_response
222
+ end
223
+ end
224
+
225
+ # Do standard validations on response
226
+ #
227
+ # Firstly, check if a valid HTTP code was returned
228
+ # Secondly, check for presence of "error" key in returned hash
229
+ def self.validate!(response)
230
+ case response.code
231
+ when 300..399 # redirects
232
+ fail ConnectionError.new('We were redirected. QT YML configuration appears to be incorrect. Verify your URL and API.')
233
+ when 400..599 # client and server errors
234
+ fail AdapterException.new(response)
235
+ end
236
+
237
+ if response_contains_error?(response)
238
+ fail AdapterException, response
239
+ end
240
+ end
241
+
242
+ def self.response_contains_error?(response)
243
+ parsed_response = response.parsed_response
244
+ parsed_response.is_a?(Hash) && parsed_response.key?('error')
245
+ end
246
+ end
247
+
248
+ class Parser
249
+ def initialize(attributes)
250
+ @attributes = attributes
251
+ end
252
+
253
+ def attributes
254
+ @attributes ||= {}
255
+ end
256
+
257
+ def parsed_attributes
258
+ @parsed_attributes ||= parse_attributes
259
+ end
260
+
261
+ private
262
+
263
+ def parse_attributes
264
+ attributes.map.with_object({}) do |(attribute, value), hash|
265
+ hash[attribute] = parse(attribute, value)
266
+ end
267
+ end
268
+
269
+ def parse(attribute, value)
270
+ return nil if value.nil?
271
+ return convert(value, :to_date) if attribute.to_s.ends_with?('_date')
272
+ # to_datetime as it converts to app time zone, to_time converts to system time zone
273
+ return convert(value, :to_datetime) if attribute.to_s.ends_with?('_time')
274
+ return convert(value, :to_datetime) if attribute.to_s.ends_with?('_at')
275
+ value
276
+ end
277
+
278
+ def convert(value, conversion_method)
279
+ convertable_value = value.is_a?(Hash) ? value['_value'] : value
280
+ convertable_value.send(conversion_method)
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,20 @@
1
+ module QuickTravel
2
+ class AdapterException < Exception
3
+ attr_reader :response
4
+
5
+ def initialize(response)
6
+ @response = if response.is_a? String
7
+ { 'error' => response }
8
+ else
9
+ response.parsed_response
10
+ end
11
+
12
+ error_message = @response.fetch('error', "We're sorry, but something went wrong. Please call us.")
13
+ super(error_message)
14
+ end
15
+
16
+ def error_type
17
+ @response.fetch('error_type', 'unspecified')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'quick_travel/adapter'
2
+ require 'geokit'
3
+
4
+ module QuickTravel
5
+ class Address < Adapter
6
+ attr_accessor :address_line1, :address_line2, :city, :country_id, :id, :post_code, :state, :country_name
7
+
8
+ def initialize(hash)
9
+ super(hash)
10
+ if @country_id == AUSTRALIA_COUNTRY_ID
11
+ @country_name = 'Australia'
12
+ end
13
+ end
14
+
15
+ def geocode
16
+ @_geocode ||= QuickTravel::Cache.cache("geocode_#{self}") {
17
+ Geokit::Geocoders::MultiGeocoder.geocode(to_s)
18
+ }
19
+ rescue Geokit::Geocoders::TooManyQueriesError
20
+ nil # do not cache, do not error
21
+ end
22
+
23
+ def to_s
24
+ "#{address_line1} #{address_line2}, #{city}, #{post_code}, #{state}, #{country_name}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'quick_travel/adapter'
2
+
3
+ module QuickTravel
4
+ class Adjustment < Adapter
5
+ attr_accessor :id, :description, :gross_in_cents
6
+ money :gross
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ require 'quick_travel/adapter'
2
+
3
+ module QuickTravel
4
+ class BackgroundCheck < Adapter
5
+ def self.check(options = {})
6
+ get_and_validate('/background_check/check.json', options)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'quick_travel/adapter'
2
+
3
+ module QuickTravel
4
+ class BedConfiguration < Adapter
5
+ attr_accessor :created_at, :creator_id, :id, :name, :overriding_fare_basis_pointer_id, :resource_id, :updated_at, :updator_id
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'quick_travel/adapter'
2
+
3
+ module QuickTravel
4
+ class BedRequirement < Adapter
5
+ attr_accessor :id, :name, :single_bed_count, :double_bed_count
6
+ end
7
+ end