Skip to content

Instantly share code, notes, and snippets.

@jlecour
Last active December 25, 2015 00:09
Show Gist options
  • Select an option

  • Save jlecour/6885880 to your computer and use it in GitHub Desktop.

Select an option

Save jlecour/6885880 to your computer and use it in GitHub Desktop.

It's an implementation of a previously submitted problem.

The models are simple Virtus objects. We can use them as plain Ruby objects.

The mapper is like a factory or a builder. It is xecuted with the data source (JSON here) and returns the models tree.

The JSON and XML parsers are just boilerplate code.

# encoding: utf-8
require 'virtus'
module Models
class Base
include Virtus.model
end
class Location < Base
attribute :location_nr, Integer
attribute :parent_location_nr, Integer
attribute :location_name, String
attribute :type, String
attribute :country_is_oa3, String
attribute :main_region, String
attribute :postal_code_range, String
attribute :latitude, BigDecimal
attribute :longitude, BigDecimal
attribute :sort_index, Integer
end
class Address < Base
attribute :street, String
attribute :postal_code, String
attribute :city, String
attribute :country_is_oa3, String
attribute :latitude, BigDecimal
attribute :longitude, BigDecimal
attribute :coordinate_reliablity, Integer
end
class Price < Base
attribute :euro_exchange_rate, BigDecimal
attribute :amount_after_tax, BigDecimal
attribute :currency, String
end
class Hotel < Base
attribute :property_number, Integer
attribute :name, String
attribute :rating_hotel_de, String
attribute :main_chain_id, Integer
attribute :overall_evaluation, BigDecimal
attribute :freenights_hotel, Boolean
attribute :internet_best_price_hotel, Boolean
attribute :package_hotel, Boolean
attribute :location_number, Integer
attribute :catering, String
attribute :total_number_of_rooms, Integer
attribute :direct_booking_link, String
attribute :distance_from_geo_coordinates, BigDecimal
attribute :address, Address
attribute :location, Location
attribute :contract_state, String
attribute :sort_index, Integer
attribute :price, Price
end
class Error < Base
attribute :error_category, String
attribute :error_code, String
attribute :lower_threshold, String
attribute :upper_threshold, String
end
class Response < Base
attribute :available_hotels, [Hotel]
attribute :web_services_interface_version, String
attribute :start_time, DateTime
attribute :token, String
attribute :internal_processing_time_in_seconds, BigDecimal
attribute :error, Error
end
end
# encoding: utf-8
require 'json_parser'
class Mapper < JSONMapper
def execute
map_response(json_doc)
end
private
def map_response(response_node)
Models::Response.new({
:web_services_interface_version => response_node.fetch('web_services_interface_version'),
:start_time => response_node.fetch('start_time'),
:token => response_node.fetch('token'),
:internal_processing_time_in_seconds => response_node.fetch('internal_processing_time_in_seconds').gsub(/,/,'.'),
}).tap {|response|
error_node = response_node.fetch("error")
response.error = map_error(error_node)
hotels_node = Array.wrap(response_node.fetch("available_hotel_list").fetch("availability_list_hotel"))
response.available_hotels = map_hotels(hotels_node)
}
end
def map_error(error_node)
Models::Error.new({
:error_category => error_node.fetch('error_category'),
:error_code => error_node.fetch('error_code'),
:lower_threshold => error_node.fetch('lower_threshold'),
:upper_threshold => error_node.fetch('upper_threshold'),
})
end
def map_hotels(hotels_node)
hotels_node.map do |hotel_node|
Models::Hotel.new({
:property_number => hotel_node.fetch('property_number'),
:name => hotel_node.fetch('name'),
:rating_hotel_de => hotel_node.fetch('rating_hotel_de'),
:main_chain_id => hotel_node.fetch('main_chain_id'),
:overall_evaluation => hotel_node.fetch('overall_evaluation'),
:freenights_hotel => hotel_node.fetch('is_freenights_hotel'),
:internet_best_price_hotel => hotel_node.fetch('is_internet_best_price_hotel'),
:package_hotel => hotel_node.fetch('is_package_hotel'),
:location_number => hotel_node.fetch('location_number'),
:catering => hotel_node.fetch('catering'),
:total_number_of_rooms => hotel_node.fetch('total_number_of_rooms'),
:direct_booking_link => hotel_node.fetch('direct_booking_link'),
:distance_from_geo_coordinates => hotel_node.fetch('distance_from_geo_coordinates'),
:contract_state => hotel_node.fetch('contract_status').fetch('contract_state'),
:sort_index => hotel_node.fetch('contract_status').fetch('sort_index'),
}).tap { |hotel|
address_node = hotel_node.fetch('hotel_address')
hotel.address = map_address(address_node)
location_node = hotel_node.fetch('location')
hotel.location = map_location(location_node)
price_node = hotel_node.fetch('price')
hotel.price = map_price(price_node)
}
end
end
def map_address(address_node)
Models::Address.new({
:street => address_node.fetch("street"),
:postal_code => address_node.fetch("postal_code"),
:city => address_node.fetch("city"),
:country_is_oa3 => address_node.fetch("country_is_oa3"),
:latitude => address_node.fetch("geographic_coordinates").fetch("latitude"),
:longitude => address_node.fetch("geographic_coordinates").fetch("longitude"),
:coordinate_reliablity => address_node.fetch("geographic_coordinates").fetch("coordinate_reliablity"),
})
end
def map_location(location_node)
Models::Location.new({
:location_nr => location_node.fetch("location_nr"),
:parent_location_nr => location_node.fetch("parent_location_nr"),
:location_name => location_node.fetch("location_name"),
:type => location_node.fetch("type"),
:country_is_oa3 => location_node.fetch("country_is_oa3"),
:main_region => location_node.fetch("main_region"),
:postal_code_range => location_node.fetch("postal_code_range"),
:latitude => location_node.fetch("latitude"),
:longitude => location_node.fetch("longitude"),
:sort_index => location_node.fetch("sort_index"),
})
end
def map_price(price_node)
Models::Price.new({
:euro_exchange_rate => price_node.fetch("@euro_exchange_rate"),
:amount_after_tax => price_node.fetch("@amount_after_tax"),
:currency => price_node.fetch("@currency"),
})
end
end
require 'multi_json' unless defined? MultiJson
class JSONMapper
attr_reader :json_doc
def self.from_json(json)
json_doc = json.respond_to?(:fetch) ? json : MultiJson.load(json)
new(json_doc).execute
end
def initialize(json_doc)
@json_doc = json_doc
end
def execute
raise NotImplementedError
end
end
{
"error": {
"error_category": "NoError",
"error_code": "NoError",
"lower_threshold": "-1",
"upper_threshold": "-1"
},
"web_services_interface_version": "2.8",
"start_time": "2013-10-08T15:49:11+02:00",
"token": "ajcyecfsjchsgxgy",
"internal_processing_time_in_seconds": "1,7316444",
"available_hotel_list": {
"availability_list_hotel": {
"property_number": "122060",
"name": "Hotel The Duke Of Leinster",
"rating_hotel_de": "3",
"media": {
"picture_reference": {
"@type": "PropertyThumbnail",
"@category": "Logo",
"@protocol": "Http",
"@is_absolute_url": "true",
"@link": "http://www.hotel.de/media/hotel/logo/logo_122060.jpg"
}
},
"hotel_address": {
"street": "20 Leinster Gardens",
"postal_code": "W2 3AN",
"city": "London",
"country_is_oa3": "GBR",
"geographic_coordinates": {
"latitude": "51.5130509611301",
"longitude": "-0.18337994813921",
"coordinate_reliablity": "88"
}
},
"chain_list": {
"chain": [{
"chain_number": "376",
"chain_name": "Hotusa Hotels",
"umbrella_chain_id": "-1",
"visible": true
}, {
"chain_number": "985",
"chain_name": "Keytel International",
"umbrella_chain_id": "-1",
"visible": true
}, {
"chain_number": "1932",
"chain_name": "Crystal Hotels",
"umbrella_chain_id": "-1",
"visible": true
}]
},
"main_chain_id": "1932",
"overall_evaluation": "6.7",
"is_freenights_hotel": false,
"is_internet_best_price_hotel": true,
"is_package_hotel": false,
"location_number": "125449",
"location": {
"location_nr": "125449",
"parent_location_nr": "133431",
"location_name": "London",
"type": "City",
"country_is_oa3": "GBR",
"main_region": "Greater London",
"postal_code_range": "BR4-WC2",
"latitude": "51.5174914694172",
"longitude": "-0.09280872371166",
"sort_index": null
},
"catering": "Breakfast",
"total_number_of_rooms": "0",
"direct_booking_link": "/Booking.aspx",
"distance_from_geo_coordinates": "6.294",
"distance_list": {
"distance": [{
"category": "Airport",
"distance_in_km": "24.3"
}, {
"category": "Highway",
"distance_in_km": "8.3"
}, {
"category": "Station",
"distance_in_km": "4.5"
}, {
"category": "Exhibition",
"distance_in_km": "5.6"
}, {
"category": "TownCentre",
"distance_in_km": "6.294"
}]
},
"contract_status": {
"contract_state": "TypeD",
"sort_index": "3501500000017"
},
"price": {
"@euro_exchange_rate": "25.5",
"@amount_after_tax": "5759.18",
"@currency": "CZK"
}
}
},
"available_hotels": "1"
}
require 'nokogiri'
class XMLMapper
attr_reader :xml_doc
def self.from_xml(xml)
xml_doc = Nokogiri::XML(xml);
new(xml_doc).execute
end
def initialize(xml_doc)
@xml_doc = xml_doc
end
def execute
raise NotImplementedError
end
protected
def striped_content(doc, path)
doc.xpath(path).text.strip
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment