zipcoder 0.3.0 → 0.4.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 +129 -48
- data/lib/ext/string.rb +10 -2
- data/lib/zipcoder/cacher/base.rb +190 -0
- data/lib/zipcoder/version.rb +1 -1
- data/lib/zipcoder.rb +103 -133
- data/spec/zipcoder_spec.rb +116 -26
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a3ed8a72bc6297d47835fa432a6ef7329afa105
|
4
|
+
data.tar.gz: dcd374d44e39bc8a7c11991a3e7e7d6ebd232c03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fe034ee31e6ee82c61d4b4aba9b0a2a61635b81e236ceafb2100e858d27e2e842e2e8c43a88db5d6234cc05b18c1b57f3bdd6c6e31e9256d1fb55f3eb3bdadf
|
7
|
+
data.tar.gz: ba288abb61c112e26c09e7788792b30123634bb69af4d93cdf0007e67c4eacd2a46f8e77bcef2c946ab283636985dcb3caa95f4a54e088d5d8684051d4bdf359
|
data/README.md
CHANGED
@@ -7,6 +7,13 @@ Gem for performing zip code lookup operations
|
|
7
7
|
|
8
8
|
## Revision History
|
9
9
|
|
10
|
+
- v0.4.0:
|
11
|
+
- abstracted "cacher" object to later support "redis" and "memcacher"
|
12
|
+
- API Change!! - changed name of "cities" to "state_cities"
|
13
|
+
- added "names_only" option to "state_cities"
|
14
|
+
- added "states" method that returns list of states
|
15
|
+
- added "zip_cities" method that returns the cities associated with a
|
16
|
+
list of zip codes
|
10
17
|
- v0.3.0:
|
11
18
|
- API Change!! - intitialization method change from "load_data" to "load_cache"
|
12
19
|
- added city and state caches
|
@@ -58,104 +65,178 @@ reduce this later.
|
|
58
65
|
The library overrides the String (and in some instances Integer) class to
|
59
66
|
provide the following methods
|
60
67
|
|
61
|
-
####
|
68
|
+
#### Method: Zipcoder.zip_info(zip, **args)
|
62
69
|
|
63
|
-
|
70
|
+
Returns the info for the "zip"
|
71
|
+
|
72
|
+
**variations:**
|
73
|
+
|
74
|
+
- ```Zipcoder.zip_info(78748, **args)```
|
75
|
+
- ```Zipcoder.zip_info("78748", **args)```
|
76
|
+
- ```"78748".zip_info(**args)```
|
77
|
+
- ```78748.zip_info(**args)```
|
78
|
+
|
79
|
+
**parameters:**
|
80
|
+
|
81
|
+
- zip [String, Integer] - a string or integer representing a single zip code
|
82
|
+
- **return** [Hash] - zip object
|
83
|
+
|
84
|
+
**arguments:**
|
85
|
+
|
86
|
+
- keys [Array] - array of keys to include (filters out the others)
|
87
|
+
|
88
|
+
**notes:**
|
89
|
+
|
90
|
+
- none
|
91
|
+
|
92
|
+
**examples:**
|
64
93
|
|
65
94
|
``` ruby
|
66
95
|
require 'zipcoder'
|
67
96
|
|
97
|
+
# Looks up a zip code by string
|
68
98
|
puts "78748".zip_info
|
69
99
|
# > {:zip=>"78748", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
100
|
+
|
101
|
+
# Looks up a zip code by integer
|
102
|
+
puts 78748.zip_info
|
103
|
+
# > {:zip=>"78748", :city=>"Austin", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
104
|
+
|
70
105
|
```
|
71
106
|
|
72
|
-
|
107
|
+
#### Method: Zipcoder.zip_cities(zip_string, **args)
|
73
108
|
|
74
|
-
|
75
|
-
require 'zipcoder'
|
109
|
+
Returns the cities that are covered by the "zip_string"
|
76
110
|
|
77
|
-
|
78
|
-
# > {:zip=>"78748", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
79
|
-
```
|
111
|
+
**variations:**
|
80
112
|
|
81
|
-
|
113
|
+
- ```Zipcoder.zip_cities("78701-78799,78613", **args)```
|
114
|
+
- ```"78701-78799,78613".zip_cities(**args)```
|
82
115
|
|
83
|
-
|
84
|
-
as shown below
|
116
|
+
**parameters:**
|
85
117
|
|
86
|
-
|
87
|
-
|
118
|
+
- zip_string [String] - a string containing comma delimited list of
|
119
|
+
zip codes and zip code ranges (ex. "78701-78750, 78613")
|
120
|
+
- **return** [Array] - array of zip objects or names (if "names_only"
|
121
|
+
is specified)
|
88
122
|
|
89
|
-
|
90
|
-
# > {:city=>"AUSTIN", :state=>"TX"}
|
91
|
-
```
|
123
|
+
**arguments:**
|
92
124
|
|
93
|
-
|
125
|
+
- keys [Array] - array of keys to include (filters out the others)
|
126
|
+
- names_only [Bool] - set to "true" if you only want the city names returned
|
127
|
+
- max [Integer] - maximum number of cities to return
|
128
|
+
|
129
|
+
**notes:**
|
94
130
|
|
95
|
-
|
131
|
+
- none
|
132
|
+
|
133
|
+
**examples:**
|
96
134
|
|
97
135
|
``` ruby
|
98
136
|
require 'zipcoder'
|
99
137
|
|
100
|
-
|
101
|
-
|
138
|
+
# Returns the cities for a zip_code
|
139
|
+
puts "78701-78750,78613".zip_cities
|
140
|
+
# > [{:zip=>"78748", :city=>"Austin", :state=>"TX", :lat=>30.26, :long=>-97.74}, ...
|
141
|
+
|
142
|
+
# Returns just the name of the cities
|
143
|
+
puts "78701-78750,78613".zip_cities names_only: true
|
144
|
+
# > ["Austin", "Cedar Park"]
|
102
145
|
```
|
103
146
|
|
104
|
-
|
147
|
+
#### Method: Zipcoder.city_info(city_state, **args)
|
148
|
+
|
149
|
+
Returns the zip object for a city
|
150
|
+
|
151
|
+
**variations:**
|
152
|
+
|
153
|
+
- ```Zipcoder.city_info("Atlanta, GA", **args)```
|
154
|
+
- ```"zip_string".zip_cities(**args)```
|
155
|
+
|
156
|
+
**parameters:**
|
157
|
+
|
158
|
+
- city_state [String] - a string "city, state"
|
159
|
+
- **return** [Hash] - zip object
|
160
|
+
|
161
|
+
**arguments:**
|
162
|
+
|
163
|
+
- keys [Array] - array of keys to include (filters out the others)
|
164
|
+
|
165
|
+
**notes:**
|
105
166
|
|
106
167
|
- the "zip", "lat", "long" are the combined values from all of the
|
107
168
|
individual zip codes
|
108
169
|
- the library will normalize the key by removing all of the whitespace
|
109
|
-
and capitalizing the letters. So for example, "
|
110
|
-
"
|
111
|
-
- the library will cache the normalized cities to improve performance
|
112
|
-
on subsequent calls
|
170
|
+
and capitalizing the letters. So for example, " Los Angeles , CA " becomes
|
171
|
+
"LOS ANGELES,CA"
|
113
172
|
|
114
|
-
|
115
|
-
|
116
|
-
You can filter the keys that are returned by including the "keys" argument
|
117
|
-
as shown below
|
173
|
+
**examples:**
|
118
174
|
|
119
175
|
``` ruby
|
120
176
|
require 'zipcoder'
|
121
177
|
|
122
|
-
puts "Austin, TX".city_info
|
123
|
-
# > {:zip=>"78701-78799"}
|
178
|
+
puts "Austin, TX".city_info
|
179
|
+
# > {:zip=>"78701-78799", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
124
180
|
```
|
125
181
|
|
126
|
-
####
|
182
|
+
#### Method: Zipcoder.state_cities(state, **args)
|
127
183
|
|
128
184
|
This will return the cities in a state
|
129
185
|
|
186
|
+
**variations:**
|
187
|
+
|
188
|
+
- ```Zipcoder.state_cities("GA", **args)```
|
189
|
+
- ```"GA".state_cities(**args)```
|
190
|
+
|
191
|
+
**parameters:**
|
192
|
+
|
193
|
+
- state [String] - the 2 letter state abbreviation
|
194
|
+
- **return** [Array] - list of zip objects (or city names if "names_only")
|
195
|
+
is set
|
196
|
+
|
197
|
+
**arguments:**
|
198
|
+
|
199
|
+
- keys [Array] - array of keys to include (filters out the others)
|
200
|
+
- names_only [Bool] - set to "true" if you only want the city names returned
|
201
|
+
|
202
|
+
**examples:**
|
203
|
+
|
130
204
|
``` ruby
|
131
205
|
require 'zipcoder'
|
132
206
|
|
133
|
-
|
134
|
-
|
207
|
+
# Returns Objects
|
208
|
+
puts "TX".state_cities
|
209
|
+
# > [{:city=>"Abbott", :state=>"TX" ...
|
210
|
+
|
211
|
+
# Returns List
|
212
|
+
puts "TX".state_cities names_only: true
|
213
|
+
# > ["Abbott", ...
|
135
214
|
```
|
136
215
|
|
137
|
-
|
216
|
+
#### Method: Zipcoder.states
|
138
217
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
218
|
+
This will return the states in the US
|
219
|
+
|
220
|
+
**variations:**
|
221
|
+
|
222
|
+
- ```Zipcoder.states```
|
223
|
+
|
224
|
+
**parameters:**
|
146
225
|
|
147
|
-
|
226
|
+
- none
|
148
227
|
|
149
|
-
|
150
|
-
|
228
|
+
**arguments:**
|
229
|
+
|
230
|
+
- none
|
231
|
+
|
232
|
+
**examples:**
|
151
233
|
|
152
234
|
``` ruby
|
153
235
|
require 'zipcoder'
|
154
236
|
|
155
|
-
puts
|
156
|
-
# >
|
237
|
+
puts Zipcoder.states
|
238
|
+
# > ["AK", "AL", ...
|
157
239
|
```
|
158
|
-
|
159
240
|
### Updating Data
|
160
241
|
|
161
242
|
The library is using the free public zip code data base located
|
data/lib/ext/string.rb
CHANGED
@@ -6,16 +6,24 @@ class String
|
|
6
6
|
Zipcoder.zip_info self, **kwargs
|
7
7
|
end
|
8
8
|
|
9
|
+
def zip_cities(**kwargs)
|
10
|
+
Zipcoder.zip_cities self, **kwargs
|
11
|
+
end
|
12
|
+
|
9
13
|
def city_info(**kwargs)
|
10
14
|
Zipcoder.city_info self, **kwargs
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
14
|
-
Zipcoder.
|
17
|
+
def state_cities(**kwargs)
|
18
|
+
Zipcoder.state_cities self, **kwargs
|
15
19
|
end
|
16
20
|
|
17
21
|
def to_zip
|
18
22
|
self
|
19
23
|
end
|
20
24
|
|
25
|
+
def capitalize_all
|
26
|
+
self.split(' ').map {|w| w.capitalize }.join(' ')
|
27
|
+
end
|
28
|
+
|
21
29
|
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Cacher
|
2
|
+
# The cacher base class places all of the objects in memory. The
|
3
|
+
# abstraction will later allow us to override for MemCacher and
|
4
|
+
# Redis implementations
|
5
|
+
class Base
|
6
|
+
|
7
|
+
#region Override These
|
8
|
+
def _init_cache(**kwargs)
|
9
|
+
@cache = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def _empty_cache
|
13
|
+
@cache.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def _write_cache(key, value)
|
17
|
+
@cache[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def _read_cache(key)
|
21
|
+
@cache[key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def _iterate_keys(**kwargs, &block)
|
25
|
+
return if block == nil
|
26
|
+
|
27
|
+
start_with = kwargs[:start_with]
|
28
|
+
|
29
|
+
@cache.keys.each do |key|
|
30
|
+
if start_with == nil or key.start_with?(start_with)
|
31
|
+
block.call(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#endregion
|
37
|
+
|
38
|
+
def initialize(**kwargs)
|
39
|
+
self._init_cache **kwargs
|
40
|
+
end
|
41
|
+
|
42
|
+
def load
|
43
|
+
this_dir = File.expand_path(File.dirname(__FILE__))
|
44
|
+
|
45
|
+
# Load zip cache from file
|
46
|
+
zip_data = File.join(this_dir, '..', '..', 'data', 'zip_data.yml')
|
47
|
+
zip_codes = YAML.load(File.open(zip_data))
|
48
|
+
|
49
|
+
# Initialize
|
50
|
+
_empty_cache
|
51
|
+
city_states = {}
|
52
|
+
state_lookup = {}
|
53
|
+
|
54
|
+
# Add the zip codes to the cache
|
55
|
+
zip_codes.each do |zip, info|
|
56
|
+
city = info[:city].capitalize_all
|
57
|
+
info[:city] = city
|
58
|
+
state = info[:state]
|
59
|
+
|
60
|
+
# Iterate through the zip codes and add them to the zip cache
|
61
|
+
_write_cache _zip_cache(zip), info
|
62
|
+
|
63
|
+
# Create the city lookups
|
64
|
+
city_state = "#{city.upcase},#{state.upcase}"
|
65
|
+
infos = city_states[city_state] || []
|
66
|
+
infos << info
|
67
|
+
city_states[city_state] = infos
|
68
|
+
end
|
69
|
+
|
70
|
+
# Normalize each city and populate the state cache
|
71
|
+
city_states.each do |city_state, infos|
|
72
|
+
city = infos[0][:city]
|
73
|
+
state = infos[0][:state]
|
74
|
+
|
75
|
+
# Populate the City Cache
|
76
|
+
normalized = _normalize_city(infos)
|
77
|
+
_write_cache _city_cache(city_state), normalized
|
78
|
+
|
79
|
+
# Populate the State Cache
|
80
|
+
cities = state_lookup[state] || []
|
81
|
+
cities << city
|
82
|
+
state_lookup[state] = cities
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set the cities cache
|
86
|
+
state_lookup.each do |state, cities|
|
87
|
+
_write_cache _state_cache(state), cities.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the states cache
|
91
|
+
self._write_cache _states, state_lookup.keys.sort
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_zip_cache(zip)
|
95
|
+
_read_cache _zip_cache(zip)
|
96
|
+
end
|
97
|
+
|
98
|
+
def read_city_cache(city_state)
|
99
|
+
_read_cache _city_cache(city_state)
|
100
|
+
end
|
101
|
+
|
102
|
+
def read_state_cache(state)
|
103
|
+
_read_cache _state_cache(state)
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_states
|
107
|
+
_read_cache _states
|
108
|
+
end
|
109
|
+
|
110
|
+
def iterate_zips(&block)
|
111
|
+
return if block == nil
|
112
|
+
_iterate_keys(start_with: "zipcoder:zip") do |key|
|
113
|
+
info = _read_cache(key)
|
114
|
+
block.call(info) if block != nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def iterate_cities(&block)
|
119
|
+
return if block == nil
|
120
|
+
_iterate_keys(start_with: "zipcoder:city") do |key|
|
121
|
+
info = _read_cache(key)
|
122
|
+
block.call(info) if block != nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def _zip_cache(zip)
|
129
|
+
"zipcoder:zip:#{zip}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def _city_cache(city_state)
|
133
|
+
"zipcoder:city:#{city_state}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def _state_cache(state)
|
137
|
+
"zipcoder:state:#{state}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def _states
|
141
|
+
"zipcoder:states"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Normalizes the values
|
145
|
+
def _normalize_city(infos)
|
146
|
+
# Values
|
147
|
+
zip_min = 100000
|
148
|
+
zip_max = 0
|
149
|
+
lat_min = 200
|
150
|
+
lat_max = -200
|
151
|
+
long_min = 200
|
152
|
+
long_max = -200
|
153
|
+
|
154
|
+
# Iterate through the info and get min/max of zip/lat/long
|
155
|
+
infos.each do |info|
|
156
|
+
zip = info[:zip].to_i
|
157
|
+
zip_min = zip if zip < zip_min
|
158
|
+
zip_max = zip if zip > zip_max
|
159
|
+
lat_min = info[:lat] if info[:lat] < lat_min
|
160
|
+
lat_max = info[:lat] if info[:lat] > lat_max
|
161
|
+
long_min = info[:long] if info[:long] < long_min
|
162
|
+
long_max = info[:long] if info[:long] > long_max
|
163
|
+
end
|
164
|
+
|
165
|
+
# Create the normalized value
|
166
|
+
if infos.count == 0
|
167
|
+
normalized = nil
|
168
|
+
elsif infos.count == 1
|
169
|
+
normalized = {
|
170
|
+
city: infos[0][:city],
|
171
|
+
state: infos[0][:state],
|
172
|
+
zip: infos[0][:zip],
|
173
|
+
lat: infos[0][:lat],
|
174
|
+
long: infos[0][:long],
|
175
|
+
}
|
176
|
+
else
|
177
|
+
normalized = {
|
178
|
+
city: infos[0][:city],
|
179
|
+
state: infos[0][:state],
|
180
|
+
zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
|
181
|
+
lat: ((lat_min+lat_max)/2).round(4),
|
182
|
+
long: ((long_min+long_max)/2).round(4)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
normalized
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
data/lib/zipcoder/version.rb
CHANGED
data/lib/zipcoder.rb
CHANGED
@@ -1,151 +1,121 @@
|
|
1
1
|
require "zipcoder/version"
|
2
|
+
require "zipcoder/cacher/base"
|
2
3
|
require "ext/string"
|
3
4
|
require "ext/integer"
|
4
5
|
require "yaml"
|
5
6
|
|
6
7
|
module Zipcoder
|
7
8
|
|
8
|
-
|
9
|
-
@@zip_cache = nil
|
10
|
-
def self.zip_cache
|
11
|
-
if @@zip_cache == nil
|
12
|
-
self.load_cache
|
13
|
-
end
|
14
|
-
@@zip_cache
|
15
|
-
end
|
16
|
-
|
17
|
-
@@city_cache = {}
|
18
|
-
def self.city_cache
|
19
|
-
@@city_cache
|
9
|
+
class ZipcoderError < Exception
|
20
10
|
end
|
21
11
|
|
22
|
-
@@
|
23
|
-
def self.
|
24
|
-
@@
|
12
|
+
@@cacher = nil
|
13
|
+
def self.cacher
|
14
|
+
if @@cacher == nil
|
15
|
+
self.load_cache
|
16
|
+
end
|
17
|
+
@@cacher
|
25
18
|
end
|
26
19
|
|
27
20
|
# Loads the data into memory
|
28
|
-
def self.load_cache
|
29
|
-
|
30
|
-
|
31
|
-
# Load zip cache from file
|
32
|
-
zip_data = File.join(this_dir, 'data', 'zip_data.yml')
|
33
|
-
@@zip_cache = YAML.load(File.open(zip_data))
|
34
|
-
|
35
|
-
# Iterate through zip codes to populate city and state data
|
36
|
-
city_states = {}
|
37
|
-
self.zip_cache.values.each do |info|
|
38
|
-
city = info[:city]
|
39
|
-
state = info[:state]
|
40
|
-
|
41
|
-
# Create the city lookups
|
42
|
-
city_state = "#{city},#{state}"
|
43
|
-
infos = city_states[city_state] || []
|
44
|
-
infos << info
|
45
|
-
city_states[city_state] = infos
|
46
|
-
end
|
47
|
-
|
48
|
-
# Normalize each city and populate the state cache
|
49
|
-
city_states.each do |city_state, infos|
|
50
|
-
state = infos[0][:state]
|
51
|
-
|
52
|
-
# Populate the City Cache
|
53
|
-
normalized = self.city_info(city_state, infos)
|
54
|
-
self.city_cache[city_state] = normalized
|
55
|
-
|
56
|
-
# Populate the State Cache
|
57
|
-
cities = self.state_cache[state] || []
|
58
|
-
cities << normalized
|
59
|
-
self.state_cache[state] = cities
|
60
|
-
end
|
61
|
-
|
62
|
-
# Sort the city arrays
|
63
|
-
self.state_cache.keys.each do |state|
|
64
|
-
infos = self.state_cache[state]
|
65
|
-
new_infos = infos.sort_by { |hsh| hsh[:city] }
|
66
|
-
self.state_cache[state] = new_infos
|
67
|
-
end
|
21
|
+
def self.load_cache(cacher=nil)
|
22
|
+
@@cacher = cacher || Cacher::Base.new
|
23
|
+
self.cacher.load
|
68
24
|
end
|
69
25
|
|
70
26
|
# Looks up zip code information
|
71
|
-
def self.zip_info(zip=nil, **kwargs
|
27
|
+
def self.zip_info(zip=nil, **kwargs)
|
72
28
|
|
73
29
|
# If zip is not nil, then we are returning a single value
|
74
30
|
if zip != nil
|
75
31
|
# Get the info
|
76
|
-
info = self.
|
77
|
-
|
78
|
-
# Inform callback that we have a match
|
79
|
-
block.call(info) if block != nil
|
32
|
+
info = self.cacher.read_zip_cache(zip.to_zip)
|
80
33
|
|
81
34
|
# Filter to the included keys
|
82
35
|
self._filter_hash_args info, kwargs[:keys]
|
83
36
|
else
|
84
37
|
# If zip is nil, then we are returning an array of values
|
85
|
-
|
86
38
|
city_filter = kwargs[:city] != nil ? kwargs[:city].upcase : nil
|
87
39
|
state_filter = kwargs[:state] != nil ? kwargs[:state].upcase : nil
|
88
40
|
|
89
41
|
# Iterate through and only add the ones that match the filters
|
90
42
|
infos = []
|
91
|
-
self.
|
92
|
-
if (city_filter == nil or info[:city] == city_filter) and
|
93
|
-
(state_filter == nil or info[:state] == state_filter)
|
43
|
+
self.cacher.iterate_zips do |info|
|
44
|
+
if (city_filter == nil or info[:city].upcase == city_filter) and
|
45
|
+
(state_filter == nil or info[:state].upcase == state_filter)
|
94
46
|
infos << self._filter_hash_args(info, kwargs[:keys])
|
95
|
-
|
96
|
-
# Inform callback that we have a match
|
97
|
-
block.call(info) if block != nil
|
98
47
|
end
|
99
|
-
|
48
|
+
end
|
100
49
|
|
101
50
|
infos
|
102
51
|
end
|
103
52
|
end
|
104
53
|
|
105
|
-
#
|
106
|
-
def self.
|
107
|
-
|
108
|
-
|
109
|
-
|
54
|
+
# Returns the cities that contain the zip codes
|
55
|
+
def self.zip_cities(zip_string, **kwargs)
|
56
|
+
max = kwargs[:max]
|
57
|
+
|
58
|
+
cities = {}
|
59
|
+
self._parse_zip_string(zip_string).each do |zip|
|
60
|
+
info = zip.zip_info
|
61
|
+
if info == nil
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
cities["#{info[:city]}, #{info[:state]}"] = true
|
110
66
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
if cached_value != nil
|
115
|
-
return self._filter_hash_args cached_value, kwargs[:keys]
|
67
|
+
if max != nil and cities.keys.count >= max
|
68
|
+
break
|
69
|
+
end
|
116
70
|
end
|
117
71
|
|
118
|
-
|
119
|
-
components = city_state.split(",")
|
120
|
-
city = components[0].strip.upcase
|
121
|
-
state = components[1].strip.upcase
|
72
|
+
cities = cities.keys.uniq.sort
|
122
73
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
74
|
+
if kwargs[:names_only]
|
75
|
+
zips = cities.map{ |x| x.split(",")[0].strip }
|
76
|
+
else
|
77
|
+
zips = []
|
78
|
+
cities.each do |key|
|
79
|
+
zips << key.city_info(keys: kwargs[:keys])
|
80
|
+
end
|
129
81
|
end
|
82
|
+
zips
|
83
|
+
end
|
130
84
|
|
131
|
-
|
132
|
-
|
85
|
+
# Looks up city information
|
86
|
+
def self.city_info(city_state, **kwargs)
|
87
|
+
# Get the city from the cache
|
88
|
+
cache_key = self._cache_key(city_state)
|
89
|
+
cached_value = self.cacher.read_city_cache(cache_key)
|
133
90
|
|
134
|
-
#
|
135
|
-
self._filter_hash_args
|
91
|
+
# Return it
|
92
|
+
self._filter_hash_args cached_value, kwargs[:keys]
|
136
93
|
end
|
137
94
|
|
138
95
|
# Returns the cities in a state
|
139
|
-
def self.
|
96
|
+
def self.state_cities(state, **kwargs)
|
140
97
|
state = state.strip.upcase
|
141
98
|
|
99
|
+
names_only = kwargs[:names_only]
|
100
|
+
keys = kwargs[:keys]
|
101
|
+
|
142
102
|
# Filter the returned cities
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
103
|
+
cities = self.cacher.read_state_cache(state)
|
104
|
+
if names_only
|
105
|
+
cities
|
106
|
+
else
|
107
|
+
infos = []
|
108
|
+
self.cacher.read_state_cache(state).each { |city|
|
109
|
+
infos << self.city_info("#{city}, #{state}", keys: keys)
|
110
|
+
}
|
147
111
|
|
148
|
-
|
112
|
+
infos
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the states
|
117
|
+
def self.states
|
118
|
+
self.cacher.read_states
|
149
119
|
end
|
150
120
|
|
151
121
|
# Filters arguments in return hash
|
@@ -160,45 +130,45 @@ module Zipcoder
|
|
160
130
|
hash
|
161
131
|
end
|
162
132
|
|
163
|
-
#
|
164
|
-
def self.
|
165
|
-
|
166
|
-
|
167
|
-
zip_max = 0
|
168
|
-
lat_min = 200
|
169
|
-
lat_max = 0
|
170
|
-
long_min = 200
|
171
|
-
long_max = 0
|
172
|
-
|
173
|
-
# Iterate through the info and get min/max of zip/lat/long
|
174
|
-
infos.each do |info|
|
175
|
-
zip = info[:zip].to_i
|
176
|
-
zip_min = zip if zip < zip_min
|
177
|
-
zip_max = zip if zip > zip_max
|
178
|
-
lat_min = info[:lat] if info[:lat] < lat_min
|
179
|
-
lat_max = info[:lat] if info[:lat] > lat_max
|
180
|
-
long_min = info[:long] if info[:long] < long_min
|
181
|
-
long_max = info[:long] if info[:long] > long_max
|
133
|
+
# Returns a cache key
|
134
|
+
def self._cache_key(city_state)
|
135
|
+
unless city_state.include? ','
|
136
|
+
raise ZipcoderError, "city/state must include ','"
|
182
137
|
end
|
183
138
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
139
|
+
components = city_state.split(',')
|
140
|
+
city = components[0].strip.upcase
|
141
|
+
state = components[1].strip.upcase
|
142
|
+
|
143
|
+
"#{city},#{state}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Parses a zip code string and returns all of the zip codes as
|
147
|
+
# an array
|
148
|
+
def self._parse_zip_string(zip_string)
|
149
|
+
zips = []
|
150
|
+
|
151
|
+
zip_string.split(",").each do |zip_component|
|
152
|
+
if zip_component.include? "-"
|
153
|
+
z = zip_component.split("-")
|
154
|
+
(z[0].strip.to_i..z[1].strip.to_i).each do |zip|
|
155
|
+
zips << self._check_zip(zip.to_zip)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
zips << self._check_zip(zip_component.strip)
|
159
|
+
end
|
199
160
|
end
|
200
161
|
|
201
|
-
|
162
|
+
zips.sort.uniq
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check the zip codes
|
166
|
+
def self._check_zip(zip)
|
167
|
+
if zip.length != 5
|
168
|
+
raise ZipcoderError, "zip code #{zip} is not 5 characters"
|
169
|
+
end
|
170
|
+
zip
|
202
171
|
end
|
203
172
|
|
173
|
+
|
204
174
|
end
|
data/spec/zipcoder_spec.rb
CHANGED
@@ -6,10 +6,19 @@ describe Zipcoder do
|
|
6
6
|
expect(Zipcoder::VERSION).not_to be nil
|
7
7
|
end
|
8
8
|
|
9
|
+
describe "#states" do
|
10
|
+
it "returns the states" do
|
11
|
+
states = described_class.states
|
12
|
+
expect(states.count).to eq(54)
|
13
|
+
expect(states[0]).to eq("AK")
|
14
|
+
expect(states[1]).to eq("AL")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
9
18
|
describe "#zip_info" do
|
10
19
|
it "returns the info for a particular zip_code" do
|
11
20
|
zip_info = described_class.zip_info "78748"
|
12
|
-
expect(zip_info[:city]).to eq("
|
21
|
+
expect(zip_info[:city]).to eq("Austin")
|
13
22
|
expect(zip_info[:state]).to eq("TX")
|
14
23
|
expect(zip_info[:zip]).to eq("78748")
|
15
24
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -18,7 +27,7 @@ describe Zipcoder do
|
|
18
27
|
|
19
28
|
it "returns the info for a particular zip_code" do
|
20
29
|
zip_info = described_class.zip_info 78748
|
21
|
-
expect(zip_info[:city]).to eq("
|
30
|
+
expect(zip_info[:city]).to eq("Austin")
|
22
31
|
expect(zip_info[:state]).to eq("TX")
|
23
32
|
expect(zip_info[:zip]).to eq("78748")
|
24
33
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -27,13 +36,13 @@ describe Zipcoder do
|
|
27
36
|
|
28
37
|
it "zero pads the zip code when using an integer" do
|
29
38
|
zip_info = described_class.zip_info 705
|
30
|
-
expect(zip_info[:city]).to eq("
|
39
|
+
expect(zip_info[:city]).to eq("Aibonito")
|
31
40
|
end
|
32
41
|
|
33
42
|
describe "keys filter" do
|
34
43
|
it "only returns specified keys" do
|
35
44
|
zip_info = described_class.zip_info "78748", keys: [:city, :state]
|
36
|
-
expect(zip_info[:city]).to eq("
|
45
|
+
expect(zip_info[:city]).to eq("Austin")
|
37
46
|
expect(zip_info[:state]).to eq("TX")
|
38
47
|
expect(zip_info[:zip]).to be_nil
|
39
48
|
expect(zip_info[:lat]).to be_nil
|
@@ -52,10 +61,15 @@ describe Zipcoder do
|
|
52
61
|
expect(zip_infos.count).to eq(1745)
|
53
62
|
end
|
54
63
|
|
55
|
-
it "returns zip codes that match a particular city
|
64
|
+
it "returns zip codes that match a particular city and state" do
|
56
65
|
zip_infos = described_class.zip_info city: "Austin", state: "TX"
|
57
66
|
expect(zip_infos.count).to eq(47)
|
58
67
|
end
|
68
|
+
|
69
|
+
it "returns zip codes that match a particular city with spaces and state" do
|
70
|
+
zip_infos = described_class.zip_info city: "San Antonio", state: "TX"
|
71
|
+
expect(zip_infos.count).to eq(65)
|
72
|
+
end
|
59
73
|
end
|
60
74
|
|
61
75
|
end
|
@@ -64,16 +78,25 @@ describe Zipcoder do
|
|
64
78
|
it "raises exception if no ','" do
|
65
79
|
expect {
|
66
80
|
described_class.city_info "Austin TX"
|
67
|
-
}.to raise_error(
|
81
|
+
}.to raise_error(Zipcoder::ZipcoderError)
|
68
82
|
end
|
69
83
|
|
70
84
|
it "returns the normalized city/state value" do
|
71
85
|
city_info = described_class.city_info "Austin, TX"
|
72
|
-
expect(city_info[:city]).to eq("
|
86
|
+
expect(city_info[:city]).to eq("Austin")
|
73
87
|
expect(city_info[:state]).to eq("TX")
|
74
88
|
expect(city_info[:zip]).to eq("78701-78799")
|
75
89
|
expect(city_info[:lat]).to eq(30.315)
|
76
|
-
expect(city_info[:long]).to eq(-
|
90
|
+
expect(city_info[:long]).to eq(-97.71)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns the normalized city/state value with space" do
|
94
|
+
city_info = described_class.city_info " San Antonio , TX"
|
95
|
+
expect(city_info[:city]).to eq("San Antonio")
|
96
|
+
expect(city_info[:state]).to eq("TX")
|
97
|
+
expect(city_info[:zip]).to eq("78201-78285")
|
98
|
+
expect(city_info[:lat]).to eq(29.435)
|
99
|
+
expect(city_info[:long]).to eq(-98.495)
|
77
100
|
end
|
78
101
|
|
79
102
|
it "returns the normalized city/state filtered" do
|
@@ -82,7 +105,7 @@ describe Zipcoder do
|
|
82
105
|
expect(city_info[:state]).to be_nil
|
83
106
|
expect(city_info[:zip]).to eq("78701-78799")
|
84
107
|
expect(city_info[:lat]).to eq(30.315)
|
85
|
-
expect(city_info[:long]).to eq(-
|
108
|
+
expect(city_info[:long]).to eq(-97.71)
|
86
109
|
end
|
87
110
|
|
88
111
|
it 'returns nil on mismatch' do
|
@@ -92,36 +115,103 @@ describe Zipcoder do
|
|
92
115
|
|
93
116
|
end
|
94
117
|
|
95
|
-
describe "#
|
118
|
+
describe "#state_cities" do
|
96
119
|
it "returns the cities for a state" do
|
97
|
-
cities = described_class.
|
120
|
+
cities = described_class.state_cities "TX"
|
98
121
|
expect(cities.count).to eq(1170)
|
99
122
|
|
100
123
|
city_info = cities[0]
|
101
|
-
expect(city_info[:city]).to eq("
|
124
|
+
expect(city_info[:city]).to eq("Abbott")
|
102
125
|
expect(city_info[:state]).to eq("TX")
|
103
126
|
expect(city_info[:zip]).to eq("76621")
|
104
127
|
expect(city_info[:lat]).to eq(31.88)
|
105
128
|
expect(city_info[:long]).to eq(-97.07)
|
106
129
|
end
|
107
130
|
it "returns the cities for a state filtered" do
|
108
|
-
cities = described_class.
|
131
|
+
cities = described_class.state_cities "TX", keys: [:zip, :city]
|
109
132
|
expect(cities.count).to eq(1170)
|
110
133
|
|
111
134
|
city_info = cities[0]
|
112
|
-
expect(city_info[:city]).to eq("
|
135
|
+
expect(city_info[:city]).to eq("Abbott")
|
113
136
|
expect(city_info[:state]).to be_nil
|
114
137
|
expect(city_info[:zip]).to eq("76621")
|
115
138
|
expect(city_info[:lat]).to be_nil
|
116
139
|
expect(city_info[:long]).to be_nil
|
117
140
|
end
|
141
|
+
it "returns the names of the cities" do
|
142
|
+
cities = described_class.state_cities "TX", names_only: true
|
143
|
+
expect(cities.count).to eq(1170)
|
144
|
+
|
145
|
+
expect(cities[0]).to eq("Abbott")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#zip_cities" do
|
150
|
+
it "returns a city for one zip code" do
|
151
|
+
zip_cities = "78748".zip_cities
|
152
|
+
expect(zip_cities.count).to eq(1)
|
153
|
+
|
154
|
+
zip_info = zip_cities[0]
|
155
|
+
expect(zip_info[:city]).to eq("Austin")
|
156
|
+
expect(zip_info[:state]).to eq("TX")
|
157
|
+
expect(zip_info[:zip]).to eq("78701-78799")
|
158
|
+
expect(zip_info[:lat]).to eq(30.315)
|
159
|
+
expect(zip_info[:long]).to eq(-97.71)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns multiple cities" do
|
163
|
+
zip_cities = "78702-78750,78613".zip_cities
|
164
|
+
expect(zip_cities.count).to eq(2)
|
165
|
+
|
166
|
+
zip_info = zip_cities[0]
|
167
|
+
expect(zip_info[:city]).to eq("Austin")
|
168
|
+
expect(zip_info[:state]).to eq("TX")
|
169
|
+
expect(zip_info[:zip]).to eq("78701-78799")
|
170
|
+
expect(zip_info[:lat]).to eq(30.315)
|
171
|
+
expect(zip_info[:long]).to eq(-97.71)
|
172
|
+
|
173
|
+
zip_info = zip_cities[1]
|
174
|
+
expect(zip_info[:city]).to eq("Cedar Park")
|
175
|
+
expect(zip_info[:state]).to eq("TX")
|
176
|
+
expect(zip_info[:zip]).to eq("78613")
|
177
|
+
expect(zip_info[:lat]).to eq(30.51)
|
178
|
+
expect(zip_info[:long]).to eq(-97.83)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "breaks on max" do
|
182
|
+
zip_cities = "13601,11223,78748,78613".zip_cities max: 2
|
183
|
+
expect(zip_cities.count).to eq(2)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "returns just names of cities sorted" do
|
187
|
+
zip_cities = "13601,78613,78702-78750".zip_cities names_only: true
|
188
|
+
expect(zip_cities).to eq(["Austin", "Cedar Park", "Watertown"])
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "#_parse_zip_string" do
|
193
|
+
it "parses the zip_string" do
|
194
|
+
[
|
195
|
+
["78703, 78701", ["78701", "78703"]],
|
196
|
+
["78701-78703, 78702", ["78701", "78702", "78703"]],
|
197
|
+
].each do |t|
|
198
|
+
zips = described_class._parse_zip_string(t[0])
|
199
|
+
expect(zips).to eq(t[1])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it "raises an error for invalid zip code" do
|
204
|
+
expect {
|
205
|
+
described_class._parse_zip_string "100"
|
206
|
+
}.to raise_error(Zipcoder::ZipcoderError)
|
207
|
+
end
|
118
208
|
end
|
119
209
|
|
120
210
|
describe("String") do
|
121
211
|
describe "zip_info" do
|
122
212
|
it "returns the info for a particular zip_code" do
|
123
213
|
zip_info = "78748".zip_info
|
124
|
-
expect(zip_info[:city]).to eq("
|
214
|
+
expect(zip_info[:city]).to eq("Austin")
|
125
215
|
expect(zip_info[:state]).to eq("TX")
|
126
216
|
expect(zip_info[:zip]).to eq("78748")
|
127
217
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -130,7 +220,7 @@ describe Zipcoder do
|
|
130
220
|
|
131
221
|
it "only returns specified keys" do
|
132
222
|
zip_info = "78748".zip_info keys: [:city, :state]
|
133
|
-
expect(zip_info[:city]).to eq("
|
223
|
+
expect(zip_info[:city]).to eq("Austin")
|
134
224
|
expect(zip_info[:state]).to eq("TX")
|
135
225
|
expect(zip_info[:zip]).to be_nil
|
136
226
|
expect(zip_info[:lat]).to be_nil
|
@@ -141,15 +231,15 @@ describe Zipcoder do
|
|
141
231
|
describe "city_info" do
|
142
232
|
it "returns the info for a particular city" do
|
143
233
|
city_info = "Austin, TX".city_info
|
144
|
-
expect(city_info[:city]).to eq("
|
234
|
+
expect(city_info[:city]).to eq("Austin")
|
145
235
|
expect(city_info[:state]).to eq("TX")
|
146
236
|
expect(city_info[:zip]).to eq("78701-78799")
|
147
237
|
expect(city_info[:lat]).to eq(30.315)
|
148
|
-
expect(city_info[:long]).to eq(-
|
238
|
+
expect(city_info[:long]).to eq(-97.71)
|
149
239
|
end
|
150
240
|
it "only returns specified keys" do
|
151
241
|
city_info = "Austin, TX".city_info keys: [:zip, :city]
|
152
|
-
expect(city_info[:city]).to eq("
|
242
|
+
expect(city_info[:city]).to eq("Austin")
|
153
243
|
expect(city_info[:state]).to be_nil
|
154
244
|
expect(city_info[:zip]).to eq("78701-78799")
|
155
245
|
expect(city_info[:lat]).to be_nil
|
@@ -157,24 +247,24 @@ describe Zipcoder do
|
|
157
247
|
end
|
158
248
|
end
|
159
249
|
|
160
|
-
describe "
|
250
|
+
describe "state_cities" do
|
161
251
|
it "returns the cities for a state" do
|
162
|
-
cities = "TX".
|
252
|
+
cities = "TX".state_cities
|
163
253
|
expect(cities.count).to eq(1170)
|
164
254
|
|
165
255
|
city_info = cities[0]
|
166
|
-
expect(city_info[:city]).to eq("
|
256
|
+
expect(city_info[:city]).to eq("Abbott")
|
167
257
|
expect(city_info[:state]).to eq("TX")
|
168
258
|
expect(city_info[:zip]).to eq("76621")
|
169
259
|
expect(city_info[:lat]).to eq(31.88)
|
170
260
|
expect(city_info[:long]).to eq(-97.07)
|
171
261
|
end
|
172
262
|
it "returns the cities for a state filtered" do
|
173
|
-
cities = "TX".
|
263
|
+
cities = "TX".state_cities keys: [:zip, :city]
|
174
264
|
expect(cities.count).to eq(1170)
|
175
265
|
|
176
266
|
city_info = cities[0]
|
177
|
-
expect(city_info[:city]).to eq("
|
267
|
+
expect(city_info[:city]).to eq("Abbott")
|
178
268
|
expect(city_info[:state]).to be_nil
|
179
269
|
expect(city_info[:zip]).to eq("76621")
|
180
270
|
expect(city_info[:lat]).to be_nil
|
@@ -186,7 +276,7 @@ describe Zipcoder do
|
|
186
276
|
describe("Integer") do
|
187
277
|
it "returns the info for a particular zip_code" do
|
188
278
|
zip_info = 78748.zip_info
|
189
|
-
expect(zip_info[:city]).to eq("
|
279
|
+
expect(zip_info[:city]).to eq("Austin")
|
190
280
|
expect(zip_info[:state]).to eq("TX")
|
191
281
|
expect(zip_info[:zip]).to eq("78748")
|
192
282
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -195,7 +285,7 @@ describe Zipcoder do
|
|
195
285
|
|
196
286
|
it "zero pads the zip code when using an integer" do
|
197
287
|
zip_info = 705.zip_info
|
198
|
-
expect(zip_info[:city]).to eq("
|
288
|
+
expect(zip_info[:city]).to eq("Aibonito")
|
199
289
|
end
|
200
290
|
end
|
201
291
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipcoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Chapman
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/ext/integer.rb
|
105
105
|
- lib/ext/string.rb
|
106
106
|
- lib/zipcoder.rb
|
107
|
+
- lib/zipcoder/cacher/base.rb
|
107
108
|
- lib/zipcoder/version.rb
|
108
109
|
- spec/spec_helper.rb
|
109
110
|
- spec/zipcoder_spec.rb
|