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 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