heycarsten-gcoder 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +3 -0
- data/README.rdoc +7 -0
- data/Rakefile +58 -0
- data/VERSION.yml +4 -0
- data/gcoder.gemspec +57 -0
- data/lib/gcoder/config.rb +26 -0
- data/lib/gcoder/geocoding_api.rb +167 -0
- data/lib/gcoder/persistence.rb +80 -0
- data/lib/gcoder/resolver.rb +15 -0
- data/lib/gcoder.rb +42 -0
- data/test/config_test.rb +37 -0
- data/test/gcoder_test.rb +26 -0
- data/test/geocoding_api_test.rb +74 -0
- data/test/persistence_test.rb +78 -0
- data/test/resolver_test.rb +36 -0
- data/test/test_helper.rb +58 -0
- metadata +74 -0
data/.document
ADDED
data/.gitignore
ADDED
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |g|
|
8
|
+
g.name = 'gcoder'
|
9
|
+
g.summary = 'A library for geocoding postal codes via the Google Maps ' \
|
10
|
+
'Geocoding API with a persisted cache through Tokyo Tyrant'
|
11
|
+
g.email = 'heycarsten@gmail.com'
|
12
|
+
g.homepage = 'http://github.com/heycarsten/gcoder'
|
13
|
+
g.authors = ['Carsten Nielsen']
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts 'Jeweler not available. Install it with: sudo gem install ' \
|
17
|
+
'technicalpickles-jeweler -s http://gems.github.com'
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/*_test.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/*_test.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort 'RCov is not available. In order to run rcov, you must: sudo gem ' \
|
39
|
+
'install spicycode-rcov'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
# require 'rake/rdoctask'
|
47
|
+
# Rake::RDocTask.new do |rdoc|
|
48
|
+
# if File.exist?('VERSION.yml')
|
49
|
+
# config = YAML.load(File.read('VERSION.yml'))
|
50
|
+
# version = [config[:major], config[:minor], config[:patch]].join('.')
|
51
|
+
# else
|
52
|
+
# version = ''
|
53
|
+
# end
|
54
|
+
# rdoc.rdoc_dir = 'rdoc'
|
55
|
+
# rdoc.title = "GCoder #{version}"
|
56
|
+
# rdoc.rdoc_files.include('README*')
|
57
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
# end
|
data/VERSION.yml
ADDED
data/gcoder.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{gcoder}
|
5
|
+
s.version = "0.3.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Carsten Nielsen"]
|
9
|
+
s.date = %q{2009-06-26}
|
10
|
+
s.email = %q{heycarsten@gmail.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"README.rdoc"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
".document",
|
16
|
+
".gitignore",
|
17
|
+
"README.rdoc",
|
18
|
+
"Rakefile",
|
19
|
+
"VERSION.yml",
|
20
|
+
"gcoder.gemspec",
|
21
|
+
"lib/gcoder.rb",
|
22
|
+
"lib/gcoder/config.rb",
|
23
|
+
"lib/gcoder/geocoding_api.rb",
|
24
|
+
"lib/gcoder/persistence.rb",
|
25
|
+
"lib/gcoder/resolver.rb",
|
26
|
+
"test/config_test.rb",
|
27
|
+
"test/gcoder_test.rb",
|
28
|
+
"test/geocoding_api_test.rb",
|
29
|
+
"test/persistence_test.rb",
|
30
|
+
"test/resolver_test.rb",
|
31
|
+
"test/test_helper.rb"
|
32
|
+
]
|
33
|
+
s.has_rdoc = true
|
34
|
+
s.homepage = %q{http://github.com/heycarsten/gcoder}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.1}
|
38
|
+
s.summary = %q{A library for geocoding postal codes via the Google Maps Geocoding API with a persisted cache through Tokyo Tyrant}
|
39
|
+
s.test_files = [
|
40
|
+
"test/config_test.rb",
|
41
|
+
"test/gcoder_test.rb",
|
42
|
+
"test/geocoding_api_test.rb",
|
43
|
+
"test/persistence_test.rb",
|
44
|
+
"test/resolver_test.rb",
|
45
|
+
"test/test_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 2
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
else
|
54
|
+
end
|
55
|
+
else
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module GCoder
|
2
|
+
module Config
|
3
|
+
|
4
|
+
# If :tt_port is 0 then :tt_host should point to a Unix socket.
|
5
|
+
@default_settings = {
|
6
|
+
:gmaps_api_key => nil,
|
7
|
+
:gmaps_api_timeout => 2,
|
8
|
+
:tt_host => nil,
|
9
|
+
:tt_port => 0,
|
10
|
+
:append_query => nil,
|
11
|
+
:no_raise_on_connection_fail => false }
|
12
|
+
|
13
|
+
def self.merge(overrides)
|
14
|
+
@default_settings.merge(overrides)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.update(hsh)
|
18
|
+
@default_settings.update(hsh)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.[](key)
|
22
|
+
@default_settings[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module GCoder
|
2
|
+
module GeocodingAPI
|
3
|
+
|
4
|
+
BASE_URI = 'http://maps.google.com/maps/geo'
|
5
|
+
BASE_PARAMS = {
|
6
|
+
:q => nil,
|
7
|
+
:output => 'json',
|
8
|
+
:oe => 'utf8',
|
9
|
+
:sensor => 'false',
|
10
|
+
:key => nil }
|
11
|
+
|
12
|
+
|
13
|
+
class Request
|
14
|
+
|
15
|
+
def self.get(query, options = {})
|
16
|
+
response = new(query, options).get
|
17
|
+
response.validate!
|
18
|
+
response.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(query, options = {})
|
22
|
+
unless query
|
23
|
+
raise Errors::BlankRequestError, "query cannot be nil"
|
24
|
+
end
|
25
|
+
unless query.is_a?(String)
|
26
|
+
raise Errors::MalformedQueryError, "query must be String, not: #{query.class}"
|
27
|
+
end
|
28
|
+
@config = Config.merge(options)
|
29
|
+
@query = query
|
30
|
+
validate_state!
|
31
|
+
end
|
32
|
+
|
33
|
+
def params
|
34
|
+
BASE_PARAMS.merge(:key => @config[:gmaps_api_key], :q => query)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_params
|
38
|
+
params.inject([]) do |array, (key, value)|
|
39
|
+
array << "#{uri_escape key}=#{uri_escape value}"
|
40
|
+
end.join('&')
|
41
|
+
end
|
42
|
+
|
43
|
+
def query
|
44
|
+
@config[:append_query] ? "#{@query} #{@config[:append_query]}" : @query
|
45
|
+
end
|
46
|
+
|
47
|
+
def uri
|
48
|
+
[BASE_URI, '?', to_params].join
|
49
|
+
end
|
50
|
+
|
51
|
+
def get
|
52
|
+
return @json_response if @json_response
|
53
|
+
Timeout.timeout(@config[:gmaps_api_timeout]) do
|
54
|
+
Response.new(http_get)
|
55
|
+
end
|
56
|
+
rescue Timeout::Error
|
57
|
+
raise Errors::RequestTimeoutError, 'The query timed out at ' \
|
58
|
+
"#{@config[:timeout]} second(s)"
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def http_get
|
64
|
+
open(uri).read
|
65
|
+
end
|
66
|
+
|
67
|
+
# Snaked from Rack::Utils which 'stole' it from Camping.
|
68
|
+
def uri_escape(string)
|
69
|
+
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
70
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
71
|
+
end.tr(' ', '+')
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_state!
|
75
|
+
if '' == query.strip.to_s
|
76
|
+
raise Errors::BlankRequestError, 'You must specifiy a query to resolve.'
|
77
|
+
end
|
78
|
+
unless @config[:gmaps_api_key]
|
79
|
+
raise Errors::NoAPIKeyError, 'You must provide a Google Maps API ' \
|
80
|
+
'key in your configuration. Go to http://code.google.com/apis/maps/' \
|
81
|
+
'signup.html to get one.'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
class Response
|
89
|
+
|
90
|
+
def initialize(raw_response)
|
91
|
+
@response = JSON.parse(raw_response)
|
92
|
+
end
|
93
|
+
|
94
|
+
def status
|
95
|
+
@response['Status']['code']
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate!
|
99
|
+
case status
|
100
|
+
when 400
|
101
|
+
raise Errors::APIMalformedRequestError, 'The GMaps Geo API has ' \
|
102
|
+
'indicated that the request is not formed correctly: ' \
|
103
|
+
"(#{@response.inspect})"
|
104
|
+
when 602
|
105
|
+
raise Errors::APIGeocodingError, 'The GMaps Geo API has indicated ' \
|
106
|
+
"that it is not able to geocode the request: (#{@response.inspect})"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_h
|
111
|
+
{ :accuracy => accuracy,
|
112
|
+
:country => {
|
113
|
+
:name => country_name,
|
114
|
+
:code => country_code,
|
115
|
+
:administrative_area => administrative_area },
|
116
|
+
:point => {
|
117
|
+
:longitude => longitude,
|
118
|
+
:latitude => latitude },
|
119
|
+
:box => box }
|
120
|
+
end
|
121
|
+
|
122
|
+
def box
|
123
|
+
{ :north => placemark['ExtendedData']['LatLonBox']['north'],
|
124
|
+
:south => placemark['ExtendedData']['LatLonBox']['south'],
|
125
|
+
:east => placemark['ExtendedData']['LatLonBox']['east'],
|
126
|
+
:west => placemark['ExtendedData']['LatLonBox']['west'] }
|
127
|
+
end
|
128
|
+
|
129
|
+
def accuracy
|
130
|
+
placemark['AddressDetails']['Accuracy']
|
131
|
+
end
|
132
|
+
|
133
|
+
def latitude
|
134
|
+
placemark['Point']['coordinates'][1]
|
135
|
+
end
|
136
|
+
|
137
|
+
def longitude
|
138
|
+
placemark['Point']['coordinates'][0]
|
139
|
+
end
|
140
|
+
|
141
|
+
def latlon_box
|
142
|
+
placemark['ExtendedData']['LatLonBox']
|
143
|
+
end
|
144
|
+
|
145
|
+
def country_name
|
146
|
+
placemark['AddressDetails']['Country']['CountryName']
|
147
|
+
end
|
148
|
+
|
149
|
+
def country_code
|
150
|
+
placemark['AddressDetails']['Country']['CountryNameCode']
|
151
|
+
end
|
152
|
+
|
153
|
+
def administrative_area
|
154
|
+
placemark['AddressDetails']['Country']['AdministrativeArea']['AdministrativeAreaName']
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def placemark
|
160
|
+
@response['Placemark'][0]
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module GCoder
|
2
|
+
module Persistence
|
3
|
+
|
4
|
+
|
5
|
+
class DataStore
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@config = Config.merge(options)
|
9
|
+
unless @config[:tt_host]
|
10
|
+
raise ArgumentError, ':tt_host must be specified when it is not ' \
|
11
|
+
'present in the global configuration.'
|
12
|
+
end
|
13
|
+
@tyrant = Rufus::Tokyo::Tyrant.new(@config[:tt_host], @config[:tt_port])
|
14
|
+
rescue RuntimeError => boom
|
15
|
+
if boom.message.include?('couldn\'t connect to tyrant')
|
16
|
+
errmsg = 'Unable to connect to the Tokyo Tyrant server at ' \
|
17
|
+
"#{@config[:tt_host]} [#{@config[:tt_port]}]"
|
18
|
+
if @config[:no_raise_on_connection_fail]
|
19
|
+
@tyrant = nil
|
20
|
+
STDERR.puts("[GCODER] #{errmsg}")
|
21
|
+
else
|
22
|
+
raise Errors::TTUnableToConnectError, errmsg
|
23
|
+
end
|
24
|
+
else
|
25
|
+
raise boom
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch(key, &block)
|
30
|
+
unless block_given?
|
31
|
+
raise ArgumentError, 'no block was given but one was expected'
|
32
|
+
end
|
33
|
+
value = storage_get(key)
|
34
|
+
return value if value
|
35
|
+
storage_put(key, block.call(key.to_s))
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
storage_get(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(key, value)
|
43
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
44
|
+
raise ArgumentError, "key must be String or Symbol, not: #{key.class}"
|
45
|
+
end
|
46
|
+
storage_put(key, value)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def storage_get(key)
|
52
|
+
if tyrant
|
53
|
+
value = tyrant[key.to_s]
|
54
|
+
value ? YAML.load(value) : nil
|
55
|
+
else
|
56
|
+
STDERR.puts "[GCODER] Unable to get #{key.inspect} " \
|
57
|
+
'because there is no Tyrant connection.'
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def storage_put(key, value)
|
63
|
+
if tyrant
|
64
|
+
tyrant[key.to_s.downcase] = YAML.dump(value)
|
65
|
+
value # <- We don't want to return YAML in this case.
|
66
|
+
else
|
67
|
+
STDERR.puts "[GCODER] Unable to put #{key.inspect} " \
|
68
|
+
'because there is no Tyrant connection.'
|
69
|
+
value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def tyrant
|
74
|
+
@tyrant
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/lib/gcoder.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rufus/tokyo/tyrant'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
$:.unshift(File.dirname(__FILE__))
|
9
|
+
|
10
|
+
require 'gcoder/config'
|
11
|
+
require 'gcoder/geocoding_api'
|
12
|
+
require 'gcoder/persistence'
|
13
|
+
require 'gcoder/resolver'
|
14
|
+
|
15
|
+
|
16
|
+
module GCoder
|
17
|
+
|
18
|
+
module Errors
|
19
|
+
class Error < StandardError; end
|
20
|
+
class MalformedQueryError < Error; end
|
21
|
+
class BlankRequestError < Error; end
|
22
|
+
class RequestTimeoutError < Error; end
|
23
|
+
class NoAPIKeyError < Error; end
|
24
|
+
class APIMalformedRequestError < Error; end
|
25
|
+
class APIGeocodingError < Error; end
|
26
|
+
class TTUnableToConnectError < Error; end
|
27
|
+
class InvalidStorageValueError < Error; end
|
28
|
+
class UnknownFormatSymbolError < Error; end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
module ProxyMethods
|
33
|
+
def GCoder.config=(hsh)
|
34
|
+
Config.update(hsh)
|
35
|
+
end
|
36
|
+
|
37
|
+
def GCoder.connect(options = {})
|
38
|
+
Resolver.new(options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'Config#merge' do
|
6
|
+
setup do
|
7
|
+
@config = GCoder::Config.merge(:gmaps_api_timeout => 3)
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'return a hash of updated config settings' do
|
11
|
+
assert_instance_of Hash, @config
|
12
|
+
assert_equal 6, @config.size
|
13
|
+
assert_equal 3, @config[:gmaps_api_timeout]
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'not change default configuration' do
|
17
|
+
assert_equal 2, GCoder::Config[:gmaps_api_timeout]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'Config#update' do
|
22
|
+
setup do
|
23
|
+
@config = GCoder::Config.update(:gmaps_api_timeout => 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'return a hash of updated config settings' do
|
27
|
+
assert_instance_of Hash, @config
|
28
|
+
assert_equal 6, @config.size
|
29
|
+
assert_equal 1, @config[:gmaps_api_timeout]
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'change default configuration' do
|
33
|
+
assert_equal 1, GCoder::Config[:gmaps_api_timeout]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/test/gcoder_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class GCoderTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'ProxyMethods' do
|
6
|
+
should 'be present in GCoder module' do
|
7
|
+
assert_respond_to GCoder, :config=
|
8
|
+
assert_respond_to GCoder, :connect
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'GCoder.config=' do
|
12
|
+
should 'proxy to Config.update' do
|
13
|
+
GCoder::Config.expects(:update).with({})
|
14
|
+
assert GCoder.config = {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'GCoder.connect' do
|
19
|
+
should 'proxy to Resolver.new' do
|
20
|
+
GCoder::Resolver.expects(:new).with({}).returns(:it_works)
|
21
|
+
assert_equal :it_works, GCoder.connect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class GeocodingAPITest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context 'initialize with incorrect arguments' do
|
7
|
+
should 'fail with no arguments' do
|
8
|
+
assert_raise ArgumentError do
|
9
|
+
GCoder::GeocodingAPI::Request.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'fail with any argument other than a string' do
|
14
|
+
assert_raise GCoder::Errors::BlankRequestError do
|
15
|
+
GCoder::GeocodingAPI::Request.new(nil)
|
16
|
+
end
|
17
|
+
assert_raise GCoder::Errors::MalformedQueryError do
|
18
|
+
GCoder::GeocodingAPI::Request.new(0)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'fail when passed a blank string as an argument' do
|
23
|
+
assert_raise GCoder::Errors::BlankRequestError do
|
24
|
+
GCoder::GeocodingAPI::Request.new(' ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'initialize with no API key present' do
|
30
|
+
should 'fall down, go boom' do
|
31
|
+
assert_raise GCoder::Errors::NoAPIKeyError do
|
32
|
+
GCoder::GeocodingAPI::Request.new('M6R2G5')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'query with correct arguments' do
|
38
|
+
setup do
|
39
|
+
@zip = GCoder::GeocodingAPI::Request.new('M6R2G5',
|
40
|
+
:gmaps_api_key => 'apikey')
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'return parsed and tidied JSON' do
|
44
|
+
@zip.expects(:http_get).returns(PAYLOADS[:json_m6r2g5])
|
45
|
+
response = @zip.get.to_h
|
46
|
+
assert_equal 5, response[:accuracy]
|
47
|
+
assert_equal 'Canada', response[:country][:name]
|
48
|
+
assert_equal 'CA', response[:country][:code]
|
49
|
+
assert_equal 'ON', response[:country][:administrative_area]
|
50
|
+
assert_equal 43.6504650, response[:point][:latitude]
|
51
|
+
assert_equal -79.4449720, response[:point][:longitude]
|
52
|
+
assert_equal 43.6536126, response[:box][:north]
|
53
|
+
assert_equal 43.6473174, response[:box][:south]
|
54
|
+
assert_equal -79.4418244, response[:box][:east]
|
55
|
+
assert_equal -79.4481196, response[:box][:west]
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'raise an error when API returns malformed request' do
|
59
|
+
@zip.expects(:http_get).returns(PAYLOADS[:json_400])
|
60
|
+
assert_raise GCoder::Errors::APIMalformedRequestError do
|
61
|
+
@zip.get.validate!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'have an appropriate URI' do
|
66
|
+
assert_match /output=json/, @zip.uri
|
67
|
+
assert_match /q=M6R2G5/, @zip.uri
|
68
|
+
assert_match /oe=u/, @zip.uri
|
69
|
+
assert_match /sensor=false/, @zip.uri
|
70
|
+
assert_match /key=apikey/, @zip.uri
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PersistenceTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'DataStore.new' do
|
6
|
+
should 'throw and argument error without :tt_host specified' do
|
7
|
+
assert_raise ArgumentError do
|
8
|
+
GCoder::Persistence::DataStore.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'throw a better error if the Tokyo Tyrant connection fails' do
|
13
|
+
assert_raise GCoder::Errors::TTUnableToConnectError do
|
14
|
+
GCoder::Persistence::DataStore.new(:tt_host => '/tmp/fake')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'DataStore' do
|
20
|
+
setup do
|
21
|
+
Rufus::Tokyo::Tyrant.stubs(:new).returns({})
|
22
|
+
@db = GCoder::Persistence::DataStore.new(:tt_host => '/tmp/tttest')
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#fetch' do
|
26
|
+
should 'raise an error without any block specified' do
|
27
|
+
assert_raise ArgumentError do
|
28
|
+
@db.fetch(:stuff)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'get a value that exists' do
|
33
|
+
@db[:test_key] = 'test_value'
|
34
|
+
assert_equal 'test_value', @db.fetch(:test_key) { raise 'ultrafail' }
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'set the value to whatever the block yields if value is nil' do
|
38
|
+
@db.fetch(:test_key) { 'block_value' }
|
39
|
+
assert_equal 'block_value', @db[:test_key]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#[]=' do
|
44
|
+
should 'now allow numbers or nil as a key' do
|
45
|
+
assert_raise ArgumentError do
|
46
|
+
@db[nil] = 'extreme fail'
|
47
|
+
end
|
48
|
+
assert_raise ArgumentError do
|
49
|
+
@db[23] = 'massive fail'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context '#[]' do
|
55
|
+
should 'return nil for a key that does not exist' do
|
56
|
+
assert_nil @db['nope']
|
57
|
+
assert_nil @db['']
|
58
|
+
assert_nil @db[0]
|
59
|
+
assert_nil @db[nil]
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'alow retrieval of nil' do
|
63
|
+
@db[:nil_key] = nil
|
64
|
+
assert_nil @db[:nil_key]
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'return parsed JSON for keys that do exist' do
|
68
|
+
@db[:payload_1] = PAYLOADS[:test_string]
|
69
|
+
@db[:payload_2] = PAYLOADS[:test_hash]
|
70
|
+
@db[:payload_3] = PAYLOADS[:test_array]
|
71
|
+
assert_equal PAYLOADS[:test_string], @db[:payload_1]
|
72
|
+
assert_equal PAYLOADS[:test_hash], @db[:payload_2]
|
73
|
+
assert_equal PAYLOADS[:test_array], @db[:payload_3]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ResolverTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'Resolver' do
|
6
|
+
setup do
|
7
|
+
Rufus::Tokyo::Tyrant.stubs(:new).returns({})
|
8
|
+
@db = GCoder::Resolver.new(:tt_host => '/tmp/tttest', :gmaps_api_key => 'testkey')
|
9
|
+
end
|
10
|
+
|
11
|
+
should 'return a hash of information for a new address' do
|
12
|
+
GCoder::GeocodingAPI::Request.any_instance.
|
13
|
+
expects(:http_get).returns(PAYLOADS[:json_m6r2g5])
|
14
|
+
assert_instance_of Hash, @db.resolve('m6r2g5')
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'not call api when a cached postal code is called' do
|
18
|
+
assert_instance_of Hash, @db.resolve('m6r2g5')
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'store the postal code key in the correct format' do
|
22
|
+
assert_instance_of Hash, @db.resolve('M6R2G5')
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'allow [] to resolve with auto-instantiation' do
|
26
|
+
assert_instance_of Hash, @db['M6R2G5']
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'raise malformed postal code error for a nil postal code' do
|
30
|
+
assert_raise GCoder::Errors::BlankRequestError do
|
31
|
+
@db.resolve(nil)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$:.unshift(File.dirname(__FILE__))
|
8
|
+
require 'gcoder'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
PAYLOADS = {
|
13
|
+
:json_m6r2g5 => %q<
|
14
|
+
{
|
15
|
+
"name": "M6R2G5",
|
16
|
+
"Status": {
|
17
|
+
"code": 200,
|
18
|
+
"request": "geocode"
|
19
|
+
},
|
20
|
+
"Placemark": [ {
|
21
|
+
"id": "p1",
|
22
|
+
"address": "Ontario M6R 2G5, Canada",
|
23
|
+
"AddressDetails": {"Country": {"CountryNameCode": "CA","CountryName": "Canada","AdministrativeArea": {"AdministrativeAreaName": "ON","PostalCode": {"PostalCodeNumber": "M6R 2G5"}}},"Accuracy": 5},
|
24
|
+
"ExtendedData": {
|
25
|
+
"LatLonBox": {
|
26
|
+
"north": 43.6536126,
|
27
|
+
"south": 43.6473174,
|
28
|
+
"east": -79.4418244,
|
29
|
+
"west": -79.4481196
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"Point": {
|
33
|
+
"coordinates": [ -79.4449720, 43.6504650, 0 ]
|
34
|
+
}
|
35
|
+
} ]
|
36
|
+
}>,
|
37
|
+
:json_602 => %q<
|
38
|
+
{
|
39
|
+
"name": "crashbangboom",
|
40
|
+
"Status": {
|
41
|
+
"code": 602,
|
42
|
+
"request": "geocode"
|
43
|
+
}
|
44
|
+
}>,
|
45
|
+
:json_400 => %q<
|
46
|
+
{
|
47
|
+
"name": "",
|
48
|
+
"Status": {
|
49
|
+
"code": 400,
|
50
|
+
"request": "geocode"
|
51
|
+
}
|
52
|
+
}>,
|
53
|
+
:test_string => "test\nstring",
|
54
|
+
:test_hash => { 'test' => 'value', 100 => 'one hundred' },
|
55
|
+
:test_array => ['test', 1, 3.1415, true, false, nil]
|
56
|
+
}
|
57
|
+
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heycarsten-gcoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Carsten Nielsen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-26 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: heycarsten@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- .document
|
26
|
+
- .gitignore
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- gcoder.gemspec
|
31
|
+
- lib/gcoder.rb
|
32
|
+
- lib/gcoder/config.rb
|
33
|
+
- lib/gcoder/geocoding_api.rb
|
34
|
+
- lib/gcoder/persistence.rb
|
35
|
+
- lib/gcoder/resolver.rb
|
36
|
+
- test/config_test.rb
|
37
|
+
- test/gcoder_test.rb
|
38
|
+
- test/geocoding_api_test.rb
|
39
|
+
- test/persistence_test.rb
|
40
|
+
- test/resolver_test.rb
|
41
|
+
- test/test_helper.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/heycarsten/gcoder
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --charset=UTF-8
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.2.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: A library for geocoding postal codes via the Google Maps Geocoding API with a persisted cache through Tokyo Tyrant
|
68
|
+
test_files:
|
69
|
+
- test/config_test.rb
|
70
|
+
- test/gcoder_test.rb
|
71
|
+
- test/geocoding_api_test.rb
|
72
|
+
- test/persistence_test.rb
|
73
|
+
- test/resolver_test.rb
|
74
|
+
- test/test_helper.rb
|