zipcoder 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 167924f1ab89a79ad0abf1e38fc06346dadc10d8
4
- data.tar.gz: b9c9d73b3facea10d02c92970fc2f7bfb8fa078c
3
+ metadata.gz: 5a3ed8a72bc6297d47835fa432a6ef7329afa105
4
+ data.tar.gz: dcd374d44e39bc8a7c11991a3e7e7d6ebd232c03
5
5
  SHA512:
6
- metadata.gz: 28eb144189cb04c3b097b5c8bb25635d2170999a89a0f3dd55c3d5f52383a6e43ca3251556e76c647abf38a52231ef10c22090f5581cfd78784fd8b93191d521
7
- data.tar.gz: 206ae14c6b611a25f0d5bd790257083684e60b9c88313543ea3c6608126e3f39c4a7d48dc24ea71e7433159199b6dcba14aae847904e4a3bed3a24846da0671e
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
- #### "zip".zip_info
68
+ #### Method: Zipcoder.zip_info(zip, **args)
62
69
 
63
- This will return information about the zip code
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
- Note that this also works with Integer zip codes, for example
107
+ #### Method: Zipcoder.zip_cities(zip_string, **args)
73
108
 
74
- ``` ruby
75
- require 'zipcoder'
109
+ Returns the cities that are covered by the "zip_string"
76
110
 
77
- puts 78748.zip_info
78
- # > {:zip=>"78748", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
79
- ```
111
+ **variations:**
80
112
 
81
- ##### "keys" argument
113
+ - ```Zipcoder.zip_cities("78701-78799,78613", **args)```
114
+ - ```"78701-78799,78613".zip_cities(**args)```
82
115
 
83
- You can filter the keys that are returned by including the "keys" argument
84
- as shown below
116
+ **parameters:**
85
117
 
86
- ``` ruby
87
- require 'zipcoder'
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
- puts "78748".zip_info(keys: [:city, :state])
90
- # > {:city=>"AUSTIN", :state=>"TX"}
91
- ```
123
+ **arguments:**
92
124
 
93
- #### "city, state".city_info
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
- This will return info about a city
131
+ - none
132
+
133
+ **examples:**
96
134
 
97
135
  ``` ruby
98
136
  require 'zipcoder'
99
137
 
100
- puts "Austin, TX".city_info
101
- # > {:zip=>"78701-78799", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
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
- Notes:
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, "Austin, TX" becomes
110
- "AUSTIN,TX"
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
- ##### "keys" argument
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(keys: [:zip])
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
- #### "state".cities
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
- puts "TX".cities
134
- # > {:zip=>"78701-7879", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
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
- Notes:
216
+ #### Method: Zipcoder.states
138
217
 
139
- - the "zip", "lat", "long" are the combined values from all of the
140
- individual zip codes
141
- - the library will normalize the key by removing all of the whitespace
142
- and capitalizing the letters. So for example, "Austin, TX" becomes
143
- "AUSTIN,TX"
144
- - the library will cache the normalized cities to improve performance
145
- on subsequent calls
218
+ This will return the states in the US
219
+
220
+ **variations:**
221
+
222
+ - ```Zipcoder.states```
223
+
224
+ **parameters:**
146
225
 
147
- ##### "keys" argument
226
+ - none
148
227
 
149
- You can filter the keys that are returned by including the "keys" argument
150
- as shown below
228
+ **arguments:**
229
+
230
+ - none
231
+
232
+ **examples:**
151
233
 
152
234
  ``` ruby
153
235
  require 'zipcoder'
154
236
 
155
- puts "Austin, TX".city_info(keys: [:zip])
156
- # > {:zip=>"78701-78799"}
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 cities(**kwargs)
14
- Zipcoder.cities self, **kwargs
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
@@ -1,3 +1,3 @@
1
1
  module Zipcoder
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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
- # Data Structure Load and Lookup
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
- @@state_cache = {}
23
- def self.state_cache
24
- @@state_cache
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
- this_dir = File.expand_path(File.dirname(__FILE__))
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, &block)
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.zip_cache[zip.to_zip]
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.zip_cache.values.each { |info|
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
- # Looks up city information
106
- def self.city_info(city_state, infos=nil, **kwargs)
107
- unless city_state.include? ','
108
- raise Exception, "city/state must include ','"
109
- end
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
- # Check the cache
112
- cache_key = city_state.delete(' ').upcase
113
- cached_value = self.city_cache[cache_key]
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
- # Cleanup city/state
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
- # Get the infos
124
- infos ||= self.zip_info(city: city, state: state)
125
- info = self._normalize_city(infos)
126
- if info != nil
127
- info[:city] = city
128
- info[:state] = state
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
- # Cache the value
132
- self.city_cache[cache_key] = info if info != nil
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
- # Filter the args
135
- self._filter_hash_args info, kwargs[:keys]
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.cities(state, **kwargs)
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
- infos = []
144
- self.state_cache[state].each { |info|
145
- infos << self._filter_hash_args(info, kwargs[:keys])
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
- infos
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
- # Normalizes the values
164
- def self._normalize_city(infos)
165
- # Values
166
- zip_min = 100000
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
- # Create the normalized value
185
- if infos.count == 0
186
- normalized = nil
187
- elsif infos.count == 1
188
- normalized = {
189
- zip: infos[0][:zip],
190
- lat: infos[0][:lat],
191
- long: infos[0][:long],
192
- }
193
- else
194
- normalized = {
195
- zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
196
- lat: (lat_min+lat_max)/2,
197
- long: (long_min+long_max)/2
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
- normalized
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
@@ -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("AUSTIN")
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("AUSTIN")
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("AIBONITO")
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("AUSTIN")
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/state" do
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(Exception)
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("AUSTIN")
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(-48.87)
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(-48.87)
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 "#cities" do
118
+ describe "#state_cities" do
96
119
  it "returns the cities for a state" do
97
- cities = described_class.cities "TX"
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("ABBOTT")
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.cities "TX", keys: [:zip, :city]
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("ABBOTT")
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("AUSTIN")
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("AUSTIN")
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("AUSTIN")
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(-48.87)
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("AUSTIN")
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 "cities" do
250
+ describe "state_cities" do
161
251
  it "returns the cities for a state" do
162
- cities = "TX".cities
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("ABBOTT")
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".cities keys: [:zip, :city]
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("ABBOTT")
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("AUSTIN")
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("AIBONITO")
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.3.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