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.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +17 -0
- data/Rakefile +18 -0
- data/bootstrap/boot.rb +4 -0
- data/examples/example_config.rb +4 -0
- data/examples/login_example.rb +25 -0
- data/lib/extensions.rb +12 -0
- data/lib/quick_travel/accommodation.rb +72 -0
- data/lib/quick_travel/adapter.rb +283 -0
- data/lib/quick_travel/adapter_exception.rb +20 -0
- data/lib/quick_travel/address.rb +27 -0
- data/lib/quick_travel/adjustment.rb +8 -0
- data/lib/quick_travel/background_check.rb +9 -0
- data/lib/quick_travel/bed_configuration.rb +7 -0
- data/lib/quick_travel/bed_requirement.rb +7 -0
- data/lib/quick_travel/booking.rb +370 -0
- data/lib/quick_travel/cache.rb +38 -0
- data/lib/quick_travel/client.rb +8 -0
- data/lib/quick_travel/client_type.rb +6 -0
- data/lib/quick_travel/config.rb +26 -0
- data/lib/quick_travel/connection_error.rb +9 -0
- data/lib/quick_travel/constants.rb +16 -0
- data/lib/quick_travel/contact.rb +5 -0
- data/lib/quick_travel/country.rb +11 -0
- data/lib/quick_travel/credit_card.rb +12 -0
- data/lib/quick_travel/discounts/booking_discount.rb +18 -0
- data/lib/quick_travel/discounts/discount.rb +21 -0
- data/lib/quick_travel/discounts/discount_tree.rb +35 -0
- data/lib/quick_travel/discounts.rb +3 -0
- data/lib/quick_travel/document.rb +20 -0
- data/lib/quick_travel/document_group.rb +14 -0
- data/lib/quick_travel/encrypt.rb +13 -0
- data/lib/quick_travel/graphic.rb +18 -0
- data/lib/quick_travel/init_from_hash.rb +16 -0
- data/lib/quick_travel/location.rb +14 -0
- data/lib/quick_travel/mock.rb +41 -0
- data/lib/quick_travel/party.rb +49 -0
- data/lib/quick_travel/passenger.rb +7 -0
- data/lib/quick_travel/passenger_price_break.rb +8 -0
- data/lib/quick_travel/passenger_type.rb +21 -0
- data/lib/quick_travel/payment.rb +41 -0
- data/lib/quick_travel/payment_type.rb +37 -0
- data/lib/quick_travel/price.rb +14 -0
- data/lib/quick_travel/product.rb +165 -0
- data/lib/quick_travel/product_configuration.rb +134 -0
- data/lib/quick_travel/product_passenger_search_criteria.rb +28 -0
- data/lib/quick_travel/product_type.rb +28 -0
- data/lib/quick_travel/property.rb +99 -0
- data/lib/quick_travel/property_facility.rb +7 -0
- data/lib/quick_travel/property_type.rb +20 -0
- data/lib/quick_travel/region.rb +21 -0
- data/lib/quick_travel/reservation.rb +110 -0
- data/lib/quick_travel/resource.rb +50 -0
- data/lib/quick_travel/room_facility.rb +7 -0
- data/lib/quick_travel/route.rb +81 -0
- data/lib/quick_travel/route_stop.rb +7 -0
- data/lib/quick_travel/search.rb +14 -0
- data/lib/quick_travel/service.rb +5 -0
- data/lib/quick_travel/trip.rb +8 -0
- data/lib/quick_travel/vehicle.rb +17 -0
- data/lib/quick_travel/vehicle_type.rb +11 -0
- data/lib/quick_travel/version.rb +3 -0
- data/lib/quick_travel.rb +59 -0
- data/lib/quicktravel_client.rb +1 -0
- data/quicktravel_client.gemspec +37 -0
- data/spec/booking_spec.rb +107 -0
- data/spec/country_spec.rb +18 -0
- data/spec/credit_card_spec.rb +11 -0
- data/spec/discounts_spec.rb +121 -0
- data/spec/passenger_type_spec.rb +32 -0
- data/spec/payment_type_spec.rb +18 -0
- data/spec/product_spec.rb +82 -0
- data/spec/region_spec.rb +29 -0
- data/spec/reservation_spec.rb +50 -0
- data/spec/resource_spec.rb +22 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/support/cassettes/booking_create.yml +50 -0
- data/spec/support/cassettes/booking_create_legacy.yml +50 -0
- data/spec/support/cassettes/booking_discounts.yml +53 -0
- data/spec/support/cassettes/booking_documents.yml +48 -0
- data/spec/support/cassettes/booking_show.yml +49 -0
- data/spec/support/cassettes/booking_update.yml +94 -0
- data/spec/support/cassettes/booking_with_discounts.yml +72 -0
- data/spec/support/cassettes/booking_with_documents.yml +117 -0
- data/spec/support/cassettes/booking_with_nested_attributes.yml +187 -0
- data/spec/support/cassettes/country_all.yml +127 -0
- data/spec/support/cassettes/create_reservation_fail.yml +47 -0
- data/spec/support/cassettes/create_reservation_with_booking.yml +53 -0
- data/spec/support/cassettes/passenger_all.yml +48 -0
- data/spec/support/cassettes/product_date_range_bookability.yml +118 -0
- data/spec/support/cassettes/product_show.yml +52 -0
- data/spec/support/cassettes/region_index.yml +48 -0
- data/spec/support/cassettes/region_show.yml +48 -0
- data/spec/support/cassettes/reservation_with_extra_picks.yml +165 -0
- data/spec/support/cassettes/resource_fare_bases.yml +97 -0
- data/spec/support/cassettes/resource_show.yml +50 -0
- 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
data/.rubocop.yml
ADDED
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
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
|
+
[](http://badge.fury.io/rb/quicktravel_client)
|
5
|
+
[](https://travis-ci.org/sealink/quicktravel_client)
|
6
|
+
[](https://coveralls.io/r/sealink/quicktravel_client)
|
7
|
+
[](https://gemnasium.com/sealink/quicktravel_client)
|
8
|
+
[](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,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
|