zipcoder 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +30 -12
- data/Rakefile +5 -24
- data/lib/data/{zip_lookup.yml → zip_data.yml} +135 -75441
- data/lib/ext/integer.rb +5 -2
- data/lib/ext/string.rb +5 -22
- data/lib/zipcoder/version.rb +1 -1
- data/lib/zipcoder.rb +118 -12
- data/spec/zipcoder_spec.rb +112 -10
- metadata +3 -4
- data/lib/data/city_lookup.yml +0 -306283
data/lib/ext/integer.rb
CHANGED
data/lib/ext/string.rb
CHANGED
@@ -3,32 +3,15 @@ require_relative "../zipcoder"
|
|
3
3
|
class String
|
4
4
|
|
5
5
|
def zip_info(**kwargs)
|
6
|
-
|
7
|
-
|
8
|
-
# Filter to the included keys
|
9
|
-
if kwargs[:keys] != nil
|
10
|
-
new_info = {}
|
11
|
-
kwargs[:keys].each { |k| new_info[k] = info[k] }
|
12
|
-
info = new_info
|
13
|
-
end
|
14
|
-
|
15
|
-
info
|
6
|
+
Zipcoder.zip_info self, **kwargs
|
16
7
|
end
|
17
8
|
|
18
9
|
def city_info(**kwargs)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
info = Zipcoder.city_lookup[key]
|
23
|
-
|
24
|
-
# Filter to the included keys
|
25
|
-
if kwargs[:keys] != nil
|
26
|
-
new_info = {}
|
27
|
-
kwargs[:keys].each { |k| new_info[k] = info[k] }
|
28
|
-
info = new_info
|
29
|
-
end
|
10
|
+
Zipcoder.city_info self, **kwargs
|
11
|
+
end
|
30
12
|
|
31
|
-
|
13
|
+
def to_zip
|
14
|
+
self
|
32
15
|
end
|
33
16
|
|
34
17
|
end
|
data/lib/zipcoder/version.rb
CHANGED
data/lib/zipcoder.rb
CHANGED
@@ -6,27 +6,133 @@ require "yaml"
|
|
6
6
|
module Zipcoder
|
7
7
|
|
8
8
|
# Data Structure Load and Lookup
|
9
|
-
@@
|
10
|
-
def self.
|
11
|
-
self.load_data if @@
|
12
|
-
@@
|
9
|
+
@@zip_data = nil
|
10
|
+
def self.zip_data
|
11
|
+
self.load_data if @@zip_data == nil
|
12
|
+
@@zip_data
|
13
13
|
end
|
14
14
|
|
15
|
-
@@
|
16
|
-
def self.
|
17
|
-
|
18
|
-
@@city_lookup
|
15
|
+
@@city_cache = {}
|
16
|
+
def self.city_cache
|
17
|
+
@@city_cache
|
19
18
|
end
|
20
19
|
|
21
20
|
# Loads the data into memory
|
22
21
|
def self.load_data
|
23
22
|
this_dir = File.expand_path(File.dirname(__FILE__))
|
24
23
|
|
25
|
-
|
26
|
-
@@
|
24
|
+
zip_data = File.join(this_dir, 'data', 'zip_data.yml')
|
25
|
+
@@zip_data = YAML.load(File.open(zip_data))
|
26
|
+
end
|
27
|
+
|
28
|
+
# Looks up zip code information
|
29
|
+
def self.zip_info(zip=nil, **kwargs, &block)
|
30
|
+
|
31
|
+
# If zip is not nil, then we are returning a single value
|
32
|
+
if zip != nil
|
33
|
+
# Get the info
|
34
|
+
info = self.zip_data[zip.to_zip]
|
35
|
+
|
36
|
+
# Inform callback that we have a match
|
37
|
+
block.call(info) if block != nil
|
38
|
+
|
39
|
+
# Filter to the included keys
|
40
|
+
self._filter_hash_args info, kwargs[:keys]
|
41
|
+
else
|
42
|
+
# If zip is nil, then we are returning an array of values
|
43
|
+
|
44
|
+
infos = []
|
45
|
+
city_filter = kwargs[:city] != nil ? kwargs[:city].upcase : nil
|
46
|
+
state_filter = kwargs[:state] != nil ? kwargs[:state].upcase : nil
|
47
|
+
|
48
|
+
# Iterate through and only add the ones that match the filters
|
49
|
+
self.zip_data.values.each { |info|
|
50
|
+
if (city_filter == nil or info[:city] == city_filter) and
|
51
|
+
(state_filter == nil or info[:state] == state_filter)
|
52
|
+
infos << self._filter_hash_args(info, kwargs[:keys])
|
53
|
+
|
54
|
+
# Inform callback that we have a match
|
55
|
+
block.call(info) if block != nil
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
infos
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Looks up city information
|
64
|
+
def self.city_info(city_state, **kwargs)
|
65
|
+
unless city_state.include? ','
|
66
|
+
raise Exception, "city/state must include ','"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Check the cache
|
70
|
+
cache_key = city_state.delete(' ').upcase
|
71
|
+
cached_value = self.city_cache[cache_key]
|
72
|
+
if cached_value != nil
|
73
|
+
return self._filter_hash_args cached_value, kwargs[:keys]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cleanup city/state
|
77
|
+
components = city_state.split(",")
|
78
|
+
city = components[0].strip.upcase
|
79
|
+
state = components[1].strip.upcase
|
80
|
+
|
81
|
+
# Normalize response
|
82
|
+
zip_min = 100000
|
83
|
+
zip_max = 0
|
84
|
+
lat_min = 200
|
85
|
+
lat_max = 0
|
86
|
+
long_min = 200
|
87
|
+
long_max = 0
|
88
|
+
|
89
|
+
# Get the infos associated with this city/state. While
|
90
|
+
# getting those, store the min/max so we can create a
|
91
|
+
# normalized version of this object
|
92
|
+
infos = self.zip_info(city: city, state: state) do |info|
|
93
|
+
zip = info[:zip].to_i
|
94
|
+
zip_min = zip if zip < zip_min
|
95
|
+
zip_max = zip if zip > zip_max
|
96
|
+
lat_min = info[:lat] if info[:lat] < lat_min
|
97
|
+
lat_max = info[:lat] if info[:lat] > lat_max
|
98
|
+
long_min = info[:long] if info[:long] < long_min
|
99
|
+
long_max = info[:long] if info[:long] > long_max
|
100
|
+
end
|
101
|
+
|
102
|
+
if infos.count == 0
|
103
|
+
# If there were no matches, return 0
|
104
|
+
info = nil
|
105
|
+
elsif infos.count == 1
|
106
|
+
# If there was 1 match, return it
|
107
|
+
info = infos[0]
|
108
|
+
else
|
109
|
+
# Create normalized object
|
110
|
+
info = {
|
111
|
+
zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
|
112
|
+
city: city,
|
113
|
+
state: state,
|
114
|
+
lat: (lat_min+lat_max)/2,
|
115
|
+
long: (long_min+long_max)/2
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
# Cache the value
|
120
|
+
self.city_cache[cache_key] = info if info != nil
|
121
|
+
|
122
|
+
# Filter the args
|
123
|
+
self._filter_hash_args info, kwargs[:keys]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Filters arguments in return hash
|
127
|
+
def self._filter_hash_args(hash, keys)
|
128
|
+
return nil if hash == nil
|
27
129
|
|
28
|
-
|
29
|
-
|
130
|
+
if keys != nil
|
131
|
+
new_hash = {}
|
132
|
+
keys.each { |k| new_hash[k] = hash[k] }
|
133
|
+
hash = new_hash
|
134
|
+
end
|
135
|
+
hash
|
30
136
|
end
|
31
137
|
|
32
138
|
end
|
data/spec/zipcoder_spec.rb
CHANGED
@@ -1,14 +1,116 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Zipcoder do
|
4
|
-
before(:all)
|
4
|
+
before(:all) do
|
5
5
|
described_class.load_data
|
6
|
-
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
described_class.city_cache.clear
|
10
|
+
end
|
7
11
|
|
8
12
|
it "has a version number" do
|
9
13
|
expect(Zipcoder::VERSION).not_to be nil
|
10
14
|
end
|
11
15
|
|
16
|
+
describe "#zip_info" do
|
17
|
+
it "returns the info for a particular zip_code" do
|
18
|
+
zip_info = described_class.zip_info "78748"
|
19
|
+
expect(zip_info[:city]).to eq("AUSTIN")
|
20
|
+
expect(zip_info[:state]).to eq("TX")
|
21
|
+
expect(zip_info[:zip]).to eq("78748")
|
22
|
+
expect(zip_info[:lat]).to eq(30.26)
|
23
|
+
expect(zip_info[:long]).to eq(-97.74)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the info for a particular zip_code" do
|
27
|
+
zip_info = described_class.zip_info 78748
|
28
|
+
expect(zip_info[:city]).to eq("AUSTIN")
|
29
|
+
expect(zip_info[:state]).to eq("TX")
|
30
|
+
expect(zip_info[:zip]).to eq("78748")
|
31
|
+
expect(zip_info[:lat]).to eq(30.26)
|
32
|
+
expect(zip_info[:long]).to eq(-97.74)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "zero pads the zip code when using an integer" do
|
36
|
+
zip_info = described_class.zip_info 705
|
37
|
+
expect(zip_info[:city]).to eq("AIBONITO")
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "keys filter" do
|
41
|
+
it "only returns specified keys" do
|
42
|
+
zip_info = described_class.zip_info "78748", keys: [:city, :state]
|
43
|
+
expect(zip_info[:city]).to eq("AUSTIN")
|
44
|
+
expect(zip_info[:state]).to eq("TX")
|
45
|
+
expect(zip_info[:zip]).to be_nil
|
46
|
+
expect(zip_info[:lat]).to be_nil
|
47
|
+
expect(zip_info[:long]).to be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "city/state filter" do
|
52
|
+
it "returns zip codes that match a particular city" do
|
53
|
+
zip_infos = described_class.zip_info city: "Austin"
|
54
|
+
expect(zip_infos.count).to eq(54)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns zip codes that match a particular state" do
|
58
|
+
zip_infos = described_class.zip_info state: "TX"
|
59
|
+
expect(zip_infos.count).to eq(1745)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns zip codes that match a particular city/state" do
|
63
|
+
zip_infos = described_class.zip_info city: "Austin", state: "TX"
|
64
|
+
expect(zip_infos.count).to eq(47)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#city_info" do
|
71
|
+
it "raises exception if no ','" do
|
72
|
+
expect {
|
73
|
+
described_class.city_info "Austin TX"
|
74
|
+
}.to raise_error(Exception)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns the normalized city/state value" do
|
78
|
+
city_info = described_class.city_info "Austin, TX"
|
79
|
+
expect(city_info[:city]).to eq("AUSTIN")
|
80
|
+
expect(city_info[:state]).to eq("TX")
|
81
|
+
expect(city_info[:zip]).to eq("78701-78799")
|
82
|
+
expect(city_info[:lat]).to eq(30.315)
|
83
|
+
expect(city_info[:long]).to eq(-48.87)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns the normalized city/state filtered" do
|
87
|
+
city_info = described_class.city_info "Austin, TX", keys: [:zip, :lat, :long]
|
88
|
+
expect(city_info[:city]).to be_nil
|
89
|
+
expect(city_info[:state]).to be_nil
|
90
|
+
expect(city_info[:zip]).to eq("78701-78799")
|
91
|
+
expect(city_info[:lat]).to eq(30.315)
|
92
|
+
expect(city_info[:long]).to eq(-48.87)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "caches the unfiltered value" do
|
96
|
+
expect {
|
97
|
+
described_class.city_info "Austin, TX", keys: [:zip, :lat, :long]
|
98
|
+
}.to change{ described_class.city_cache.values.count }.by(1)
|
99
|
+
|
100
|
+
expect {
|
101
|
+
described_class.city_info "Austin, TX"
|
102
|
+
}.to change{ described_class.city_cache.values.count }.by(0)
|
103
|
+
|
104
|
+
city_info = described_class.city_info "Austin, TX"
|
105
|
+
expect(city_info[:city]).to eq("AUSTIN")
|
106
|
+
expect(city_info[:state]).to eq("TX")
|
107
|
+
expect(city_info[:zip]).to eq("78701-78799")
|
108
|
+
expect(city_info[:lat]).to eq(30.315)
|
109
|
+
expect(city_info[:long]).to eq(-48.87)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
12
114
|
describe("String") do
|
13
115
|
describe "zip lookup" do
|
14
116
|
it "returns the info for a particular zip_code" do
|
@@ -21,7 +123,7 @@ describe Zipcoder do
|
|
21
123
|
end
|
22
124
|
|
23
125
|
it "only returns specified keys" do
|
24
|
-
zip_info = "78748".zip_info
|
126
|
+
zip_info = "78748".zip_info keys: [:city, :state]
|
25
127
|
expect(zip_info[:city]).to eq("AUSTIN")
|
26
128
|
expect(zip_info[:state]).to eq("TX")
|
27
129
|
expect(zip_info[:zip]).to be_nil
|
@@ -35,17 +137,17 @@ describe Zipcoder do
|
|
35
137
|
city_info = "Austin, TX".city_info
|
36
138
|
expect(city_info[:city]).to eq("AUSTIN")
|
37
139
|
expect(city_info[:state]).to eq("TX")
|
38
|
-
expect(city_info[:
|
39
|
-
expect(city_info[:
|
40
|
-
expect(city_info[:
|
140
|
+
expect(city_info[:zip]).to eq("78701-78799")
|
141
|
+
expect(city_info[:lat]).to eq(30.315)
|
142
|
+
expect(city_info[:long]).to eq(-48.87)
|
41
143
|
end
|
42
144
|
it "only returns specified keys" do
|
43
|
-
city_info = "Austin, TX".city_info
|
145
|
+
city_info = "Austin, TX".city_info keys: [:zip, :city]
|
44
146
|
expect(city_info[:city]).to eq("AUSTIN")
|
45
147
|
expect(city_info[:state]).to be_nil
|
46
|
-
expect(city_info[:
|
47
|
-
expect(city_info[:
|
48
|
-
expect(city_info[:
|
148
|
+
expect(city_info[:zip]).to eq("78701-78799")
|
149
|
+
expect(city_info[:lat]).to be_nil
|
150
|
+
expect(city_info[:long]).to be_nil
|
49
151
|
end
|
50
152
|
end
|
51
153
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipcoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Chapman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -99,8 +99,7 @@ files:
|
|
99
99
|
- bin/console
|
100
100
|
- bin/setup
|
101
101
|
- circle.yml
|
102
|
-
- lib/data/
|
103
|
-
- lib/data/zip_lookup.yml
|
102
|
+
- lib/data/zip_data.yml
|
104
103
|
- lib/data/zipcode.csv
|
105
104
|
- lib/ext/integer.rb
|
106
105
|
- lib/ext/string.rb
|