quicktravel_client 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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