global_weather 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +86 -0
- data/Rakefile +18 -0
- data/TODO +5 -0
- data/global_weather.gemspec +29 -0
- data/lib/global_weather.rb +11 -0
- data/lib/global_weather/client.rb +29 -0
- data/lib/global_weather/country.rb +49 -0
- data/lib/global_weather/errors.rb +10 -0
- data/lib/global_weather/utils.rb +21 -0
- data/lib/global_weather/version.rb +3 -0
- data/lib/global_weather/weather.rb +85 -0
- data/test/functional/country_functional_test.rb +26 -0
- data/test/functional/weather_functional_test.rb +47 -0
- data/test/test_helper.rb +17 -0
- data/test/unit/utils_test.rb +16 -0
- data/test/unit/weather_test.rb +51 -0
- data/test/vcr/vcr_cassettes/cities_by_country_basic_call.yml +197 -0
- data/test/vcr/vcr_cassettes/cities_by_country_invalid_country.yml +49 -0
- data/test/vcr/vcr_cassettes/get_weather_basic_call.yml +56 -0
- data/test/vcr/vcr_cassettes/get_weather_invalid_1.yml +48 -0
- data/test/vcr/vcr_cassettes/get_weather_invalid_2.yml +48 -0
- data/test/vcr/vcr_cassettes/get_weather_invalid_3.yml +48 -0
- data/wsdl/globalweather.asmx.xml +209 -0
- metadata +211 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Eugen Ciur
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# GlobalWeather
|
2
|
+
|
3
|
+
[Global Weather SOAP
|
4
|
+
service](http://www.webservicex.net/WS/WSDetails.aspx?WSID=56&CATID=12)
|
5
|
+
ruby wrapper.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'global_weather'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install global_weather
|
20
|
+
|
21
|
+
This gem was tested on ruby 1.9.3p327
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Basically GlobalWeather gem provides 2 classes Country (wraps
|
26
|
+
GetCitiesByCountry soap method call) and Weather (wraps GetWeather method call)
|
27
|
+
|
28
|
+
To get cities by country (e.g. Germany)
|
29
|
+
|
30
|
+
|
31
|
+
country = GlobalWeather::Country.new 'Germany'
|
32
|
+
|
33
|
+
country.cities # returns an array of strings
|
34
|
+
|
35
|
+
|
36
|
+
In order to get current weather (e.g. country 'Germany', city 'Berlin')
|
37
|
+
|
38
|
+
|
39
|
+
weather = GlobalWeather::Weather.new 'Germany', 'Berlin'
|
40
|
+
|
41
|
+
puts weather.time[:UTC]
|
42
|
+
|
43
|
+
puts weather.temperature[:C]
|
44
|
+
|
45
|
+
puts weather.temperature[:F]
|
46
|
+
|
47
|
+
puts weather.pressure[:Hg]
|
48
|
+
|
49
|
+
puts weather.pressure[:hPa]
|
50
|
+
|
51
|
+
|
52
|
+
See full list of attributes
|
53
|
+
|
54
|
+
|
55
|
+
GlobalWeather::Weather::ATTRIBUTES
|
56
|
+
|
57
|
+
|
58
|
+
Can it be simplier?
|
59
|
+
|
60
|
+
## Test
|
61
|
+
|
62
|
+
Command
|
63
|
+
|
64
|
+
|
65
|
+
$ rake
|
66
|
+
|
67
|
+
or
|
68
|
+
|
69
|
+
|
70
|
+
$ rake test
|
71
|
+
|
72
|
+
will run all tests
|
73
|
+
|
74
|
+
|
75
|
+
## Configuration
|
76
|
+
|
77
|
+
There is only one client instance shared among all instances of GlobalWeather
|
78
|
+
Weather and Country objects.
|
79
|
+
|
80
|
+
GlobalWeather::Client.configure do |config|
|
81
|
+
config.log false
|
82
|
+
config.proxy 'http://my_company_internal_proxy'
|
83
|
+
end
|
84
|
+
|
85
|
+
options provided this way will be passed futher down to Savon.client. See full list of all
|
86
|
+
at [savon documentation](http://savonrb.com/version2/globals.html)
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
6
|
+
test.libs << 'lib' << 'test'
|
7
|
+
test.pattern = 'test/**/*_test.rb'
|
8
|
+
test.verbose = false
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
Rake::TestTask.new(:test_unit) do |test|
|
13
|
+
test.libs << 'lib' << 'test'
|
14
|
+
test.pattern = 'test/unit/**/*_test.rb'
|
15
|
+
test.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :test
|
data/TODO
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'global_weather/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "global_weather"
|
8
|
+
spec.version = GlobalWeather::VERSION
|
9
|
+
spec.authors = ["Eugen Ciur"]
|
10
|
+
spec.email = ["ciur.eugen@gmail.com"]
|
11
|
+
spec.description = %q{GlobalWeather SOAP service wrapper}
|
12
|
+
spec.summary = %q{Provides Weather and Country objects to use easy GlobalWeather service}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT or Do whatever you want with this gem!"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', "~> 1.3"
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'debugger'
|
24
|
+
spec.add_development_dependency 'vcr'
|
25
|
+
spec.add_development_dependency 'webmock', '~> 1.8'
|
26
|
+
spec.add_dependency 'excon', '0.22'
|
27
|
+
spec.add_dependency 'savon', '~> 2.0'
|
28
|
+
spec.add_dependency 'nori', "~> 2.3.0"
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require "global_weather/version"
|
3
|
+
require "global_weather/utils"
|
4
|
+
require "global_weather/client"
|
5
|
+
require "global_weather/errors"
|
6
|
+
require "global_weather/country"
|
7
|
+
require "global_weather/weather"
|
8
|
+
|
9
|
+
module GlobalWeather
|
10
|
+
include Errors
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GlobalWeather
|
2
|
+
|
3
|
+
class Client
|
4
|
+
@@client = nil
|
5
|
+
@@config = {}
|
6
|
+
|
7
|
+
def self.connect(options = {})
|
8
|
+
|
9
|
+
@@config.merge!(options)
|
10
|
+
|
11
|
+
if @@client.nil?
|
12
|
+
@@client = Savon.client({wsdl: Utils.local_wsdl_file}.merge(@@config))
|
13
|
+
else
|
14
|
+
@@client
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure(&block)
|
19
|
+
class_eval &block
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def method_missing(name, *args)
|
24
|
+
@@config[name.to_sym] = args[0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module GlobalWeather
|
2
|
+
class Country
|
3
|
+
|
4
|
+
attr_reader :name, :cities
|
5
|
+
|
6
|
+
def initialize(name = nil, options = {})
|
7
|
+
|
8
|
+
client = Client.connect(options)
|
9
|
+
|
10
|
+
raise Errors::CountryNotProvided if name.nil?
|
11
|
+
|
12
|
+
@name = name
|
13
|
+
|
14
|
+
response = client.call(:get_cities_by_country) do |locals|
|
15
|
+
locals.message 'CountryName' => name
|
16
|
+
end
|
17
|
+
|
18
|
+
if response.success?
|
19
|
+
body = response.hash[:envelope] && response.hash[:envelope][:body]
|
20
|
+
if body
|
21
|
+
result = body[:get_cities_by_country_response] && body[:get_cities_by_country_response][:get_cities_by_country_result]
|
22
|
+
create_attributes(result)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
raise Errors::RequestFailure, 'Request to SOAP service failed'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
include Utils
|
32
|
+
|
33
|
+
# Instaniates attribute 'cites' with an array of strings (cities)
|
34
|
+
def create_attributes(result)
|
35
|
+
hrep = Nori.new.parse(result)
|
36
|
+
new_data_set = hrep && hrep['NewDataSet']
|
37
|
+
if new_data_set
|
38
|
+
hash_of_cities = new_data_set && new_data_set['Table']
|
39
|
+
if hash_of_cities
|
40
|
+
@cities = hash_of_cities.map{|item| item['City']}
|
41
|
+
else
|
42
|
+
raise Errors::InvalidResponseFormat, 'Unexpected response format'
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise Errors::CountryInvalid, 'Returned data set is empty, probably because provided country is invalid'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module GlobalWeather
|
2
|
+
module Errors
|
3
|
+
class CityNotProvided < Exception; end
|
4
|
+
class CountryNotProvided < Exception; end
|
5
|
+
class CityOrCountryInvalid < Exception; end
|
6
|
+
class CountryInvalid < Exception; end
|
7
|
+
class InvalidResponseFormat < Exception; end
|
8
|
+
class RequestFailure < Exception; end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GlobalWeather
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
# Returns local copy of wsdl schema file
|
5
|
+
def self.local_wsdl_file
|
6
|
+
current_file = File.expand_path(File.dirname(__FILE__))
|
7
|
+
File.open(File.join(current_file,'..','..','wsdl','globalweather.asmx.xml'))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Camelize input string
|
11
|
+
def camelize(lower_case_and_underscored_word)
|
12
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Removes <?xml ... /xml> header from xml string
|
16
|
+
def fix_header!(string_xml)
|
17
|
+
string_xml.gsub!(/\<\?.+\?\>/,'')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module GlobalWeather
|
2
|
+
class Weather
|
3
|
+
|
4
|
+
ATTRIBUTES = %w(location time wind temperature
|
5
|
+
dew_point relative_humidity pressure sky_conditions
|
6
|
+
visibility)
|
7
|
+
|
8
|
+
attr_reader *ATTRIBUTES
|
9
|
+
|
10
|
+
def initialize(country = nil, city = nil, options = {})
|
11
|
+
|
12
|
+
client = Client.connect(options)
|
13
|
+
|
14
|
+
raise Errors::CityNotProvided if city.nil?
|
15
|
+
raise Errors::CountryNotProvided if country.nil?
|
16
|
+
|
17
|
+
response = client.call(:get_weather) do |locals|
|
18
|
+
locals.message 'CountryName' => country, 'CityName' => city
|
19
|
+
end
|
20
|
+
|
21
|
+
if response.success?
|
22
|
+
body = response.hash[:envelope] && response.hash[:envelope][:body]
|
23
|
+
if body
|
24
|
+
result = body[:get_weather_response] && body[:get_weather_response][:get_weather_result]
|
25
|
+
create_attributes(result)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
raise Errors::RequestFailure, 'Request to SOAP service failed'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
include Utils
|
35
|
+
|
36
|
+
def create_attributes(string_xml_or_hash)
|
37
|
+
# Current weather information are returning as xml string instead of hash
|
38
|
+
# That is why I parse it here with Nori into a hash
|
39
|
+
hrep = if string_xml_or_hash.is_a?(String)
|
40
|
+
raise Errors::CityOrCountryInvalid if string_xml_or_hash =~ /Data Not Found/
|
41
|
+
Nori.new.parse(fix_header!(string_xml_or_hash))
|
42
|
+
# in case service will return result as hash, do nothing
|
43
|
+
elsif string_xml_or_hash.is_a?(Hash)
|
44
|
+
string_xml_or_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
ATTRIBUTES.each do |attribute|
|
48
|
+
# camelize method is included from Utils
|
49
|
+
instance_variable_set("@#{attribute}", hrep['CurrentWeather'][camelize(attribute)])
|
50
|
+
end
|
51
|
+
|
52
|
+
@temperature = Weather.convert_temperature_to_hash(@temperature)
|
53
|
+
@time = Weather.convert_time_to_datetime(@time)
|
54
|
+
@dew_point = Weather.convert_temperature_to_hash(@dew_point)
|
55
|
+
@pressure = Weather.convert_pressure_to_hash(@pressure)
|
56
|
+
@relative_humidity = @relative_humidity.to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.convert_temperature_to_hash(input)
|
60
|
+
output = {}
|
61
|
+
/\s*(?<fahrenheit>\-?\d+) F\s*\((?<celsius>\-?\d+) C\)/ =~ input
|
62
|
+
output[:c] = output[:C] = celsius.to_i
|
63
|
+
output[:f] = output[:F] = fahrenheit.to_i
|
64
|
+
output
|
65
|
+
end
|
66
|
+
# Aug 24, 2013 - 11:20 PM EDT / 2013.08.25 0320 UTC&
|
67
|
+
def self.convert_time_to_datetime(input)
|
68
|
+
output = {}
|
69
|
+
edt_and_utc = input.split('/')
|
70
|
+
if edt_and_utc
|
71
|
+
output[:EDT] = output[:edt] = DateTime.parse(edt_and_utc[0].strip) unless edt_and_utc[0].nil?
|
72
|
+
output[:UTC] = output[:utc] = DateTime.strptime(edt_and_utc[1].strip,'%Y.%m.%d %H%M UTC') unless edt_and_utc[1].nil?
|
73
|
+
end
|
74
|
+
output
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.convert_pressure_to_hash(input)
|
78
|
+
output = {}
|
79
|
+
/\s*(?<hg>\d+\.*\d* in\. Hg) \s*\((?<hpa>\d+\.*\d*) hPa\)/ =~ input
|
80
|
+
output[:Hg] = output[:hg] = hg.to_f
|
81
|
+
output[:hPa] = output[:hpa] = hpa.to_f
|
82
|
+
output
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class CountryPositiveTests < Test::Unit::TestCase
|
4
|
+
def test_basic_call
|
5
|
+
VCR.use_cassette('cities_by_country_basic_call') do
|
6
|
+
country = GlobalWeather::Country.new 'Germany'
|
7
|
+
assert country.cities.include?('Hannover'), "Following list of cities #{country.cities} does not include Hannover"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class CountryNegativeTests < Test::Unit::TestCase
|
13
|
+
def test_no_arguments_provided
|
14
|
+
assert_raise GlobalWeather::CountryNotProvided do
|
15
|
+
GlobalWeather::Country.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_invalid_country_provided
|
20
|
+
VCR.use_cassette('cities_by_country_invalid_country') do
|
21
|
+
assert_raise GlobalWeather::CountryInvalid do
|
22
|
+
GlobalWeather::Country.new 'FarFarFarAway'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class GlobalWeatherPositiveTests < Test::Unit::TestCase
|
4
|
+
def test_basic_call
|
5
|
+
# This is REAL service call
|
6
|
+
weather = GlobalWeather::Weather.new 'Germany', 'Berlin', log: false
|
7
|
+
|
8
|
+
assert !weather.time.nil?
|
9
|
+
assert !weather.location.nil?
|
10
|
+
assert !weather.temperature.nil?
|
11
|
+
assert !weather.pressure.nil?
|
12
|
+
assert !weather.relative_humidity.nil?
|
13
|
+
assert !weather.dew_point.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class GlobalWeatherNegativeTests < Test::Unit::TestCase
|
18
|
+
def test_no_arguments_provided
|
19
|
+
assert_raise GlobalWeather::CityNotProvided, GlobalWeather::CountryNotProvided do
|
20
|
+
GlobalWeather::Weather.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_city_or_country_invalid_1
|
25
|
+
VCR.use_cassette('get_weather_invalid_1') do
|
26
|
+
assert_raise GlobalWeather::CityOrCountryInvalid do
|
27
|
+
GlobalWeather::Weather.new 'Blah', 'Berlin'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_city_or_country_invalid_2
|
33
|
+
VCR.use_cassette('get_weather_invalid_2') do
|
34
|
+
assert_raise GlobalWeather::CityOrCountryInvalid do
|
35
|
+
GlobalWeather::Weather.new 'Germany', 'Blah'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_city_or_country_invalid_3
|
41
|
+
VCR.use_cassette('get_weather_invalid_3') do
|
42
|
+
assert_raise GlobalWeather::CityOrCountryInvalid do
|
43
|
+
GlobalWeather::Weather.new 'Blah', 'Blah'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|