quicktravel_client 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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,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
|