zipcoder 0.2.0 → 0.3.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: 81f8212570017ee9537ad0ff17af68e6d4abec2d
4
- data.tar.gz: c24abbd919537c6ebcc4d9f21f72abff19979613
3
+ metadata.gz: 167924f1ab89a79ad0abf1e38fc06346dadc10d8
4
+ data.tar.gz: b9c9d73b3facea10d02c92970fc2f7bfb8fa078c
5
5
  SHA512:
6
- metadata.gz: b6c6b76529f9741f02b1e46c743452b496483d72b89e2c4d0cb164646aa5764d6fd2ed3e9e63760cfc12f98606bc96066fb66ea50be1a12b5c3ca8906301b069
7
- data.tar.gz: 16f706497a1007b065a70aa50bcc08c3b94c7934da54f682601751065b8ce47727922a993089d11d7273f1e534e82eb0c861787f24b6131a2be73ea5006661e3
6
+ metadata.gz: 28eb144189cb04c3b097b5c8bb25635d2170999a89a0f3dd55c3d5f52383a6e43ca3251556e76c647abf38a52231ef10c22090f5581cfd78784fd8b93191d521
7
+ data.tar.gz: 206ae14c6b611a25f0d5bd790257083684e60b9c88313543ea3c6608126e3f39c4a7d48dc24ea71e7433159199b6dcba14aae847904e4a3bed3a24846da0671e
data/README.md CHANGED
@@ -7,6 +7,10 @@ Gem for performing zip code lookup operations
7
7
 
8
8
  ## Revision History
9
9
 
10
+ - v0.3.0:
11
+ - API Change!! - intitialization method change from "load_data" to "load_cache"
12
+ - added city and state caches
13
+ - added "cities" call
10
14
  - v0.2.0:
11
15
  - Internal code rework
12
16
  - API Change!! - changed "city,state" to return normalized info rather
@@ -42,17 +46,19 @@ when application is loaded, you can do the following RAILS example
42
46
 
43
47
  ``` ruby
44
48
  require 'zipcoder'
45
- Zipcoder.load_data
49
+ Zipcoder.load_cache
46
50
  ```
47
51
 
48
- This will immediately load the data structures
52
+ This will immediately load the data structures. Currently it takes roughly 3s
53
+ to create and import all of the data structures. I will look at ways to
54
+ reduce this later.
49
55
 
50
56
  ### Methods
51
57
 
52
58
  The library overrides the String (and in some instances Integer) class to
53
59
  provide the following methods
54
60
 
55
- #### Lookup Zip Code
61
+ #### "zip".zip_info
56
62
 
57
63
  This will return information about the zip code
58
64
 
@@ -84,7 +90,7 @@ puts "78748".zip_info(keys: [:city, :state])
84
90
  # > {:city=>"AUSTIN", :state=>"TX"}
85
91
  ```
86
92
 
87
- #### Lookup City
93
+ #### "city, state".city_info
88
94
 
89
95
  This will return info about a city
90
96
 
@@ -117,6 +123,39 @@ puts "Austin, TX".city_info(keys: [:zip])
117
123
  # > {:zip=>"78701-78799"}
118
124
  ```
119
125
 
126
+ #### "state".cities
127
+
128
+ This will return the cities in a state
129
+
130
+ ``` ruby
131
+ require 'zipcoder'
132
+
133
+ puts "TX".cities
134
+ # > {:zip=>"78701-7879", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
135
+ ```
136
+
137
+ Notes:
138
+
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
146
+
147
+ ##### "keys" argument
148
+
149
+ You can filter the keys that are returned by including the "keys" argument
150
+ as shown below
151
+
152
+ ``` ruby
153
+ require 'zipcoder'
154
+
155
+ puts "Austin, TX".city_info(keys: [:zip])
156
+ # > {:zip=>"78701-78799"}
157
+ ```
158
+
120
159
  ### Updating Data
121
160
 
122
161
  The library is using the free public zip code data base located
data/lib/ext/string.rb CHANGED
@@ -10,6 +10,10 @@ class String
10
10
  Zipcoder.city_info self, **kwargs
11
11
  end
12
12
 
13
+ def cities(**kwargs)
14
+ Zipcoder.cities self, **kwargs
15
+ end
16
+
13
17
  def to_zip
14
18
  self
15
19
  end
@@ -1,3 +1,3 @@
1
1
  module Zipcoder
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/zipcoder.rb CHANGED
@@ -6,10 +6,12 @@ require "yaml"
6
6
  module Zipcoder
7
7
 
8
8
  # Data Structure Load and Lookup
9
- @@zip_data = nil
10
- def self.zip_data
11
- self.load_data if @@zip_data == nil
12
- @@zip_data
9
+ @@zip_cache = nil
10
+ def self.zip_cache
11
+ if @@zip_cache == nil
12
+ self.load_cache
13
+ end
14
+ @@zip_cache
13
15
  end
14
16
 
15
17
  @@city_cache = {}
@@ -17,12 +19,52 @@ module Zipcoder
17
19
  @@city_cache
18
20
  end
19
21
 
22
+ @@state_cache = {}
23
+ def self.state_cache
24
+ @@state_cache
25
+ end
26
+
20
27
  # Loads the data into memory
21
- def self.load_data
28
+ def self.load_cache
22
29
  this_dir = File.expand_path(File.dirname(__FILE__))
23
30
 
31
+ # Load zip cache from file
24
32
  zip_data = File.join(this_dir, 'data', 'zip_data.yml')
25
- @@zip_data = YAML.load(File.open(zip_data))
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
26
68
  end
27
69
 
28
70
  # Looks up zip code information
@@ -31,7 +73,7 @@ module Zipcoder
31
73
  # If zip is not nil, then we are returning a single value
32
74
  if zip != nil
33
75
  # Get the info
34
- info = self.zip_data[zip.to_zip]
76
+ info = self.zip_cache[zip.to_zip]
35
77
 
36
78
  # Inform callback that we have a match
37
79
  block.call(info) if block != nil
@@ -41,12 +83,12 @@ module Zipcoder
41
83
  else
42
84
  # If zip is nil, then we are returning an array of values
43
85
 
44
- infos = []
45
86
  city_filter = kwargs[:city] != nil ? kwargs[:city].upcase : nil
46
87
  state_filter = kwargs[:state] != nil ? kwargs[:state].upcase : nil
47
88
 
48
89
  # Iterate through and only add the ones that match the filters
49
- self.zip_data.values.each { |info|
90
+ infos = []
91
+ self.zip_cache.values.each { |info|
50
92
  if (city_filter == nil or info[:city] == city_filter) and
51
93
  (state_filter == nil or info[:state] == state_filter)
52
94
  infos << self._filter_hash_args(info, kwargs[:keys])
@@ -61,7 +103,7 @@ module Zipcoder
61
103
  end
62
104
 
63
105
  # Looks up city information
64
- def self.city_info(city_state, **kwargs)
106
+ def self.city_info(city_state, infos=nil, **kwargs)
65
107
  unless city_state.include? ','
66
108
  raise Exception, "city/state must include ','"
67
109
  end
@@ -78,7 +120,49 @@ module Zipcoder
78
120
  city = components[0].strip.upcase
79
121
  state = components[1].strip.upcase
80
122
 
81
- # Normalize response
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
129
+ end
130
+
131
+ # Cache the value
132
+ self.city_cache[cache_key] = info if info != nil
133
+
134
+ # Filter the args
135
+ self._filter_hash_args info, kwargs[:keys]
136
+ end
137
+
138
+ # Returns the cities in a state
139
+ def self.cities(state, **kwargs)
140
+ state = state.strip.upcase
141
+
142
+ # Filter the returned cities
143
+ infos = []
144
+ self.state_cache[state].each { |info|
145
+ infos << self._filter_hash_args(info, kwargs[:keys])
146
+ }
147
+
148
+ infos
149
+ end
150
+
151
+ # Filters arguments in return hash
152
+ def self._filter_hash_args(hash, keys)
153
+ return nil if hash == nil
154
+
155
+ if keys != nil
156
+ new_hash = {}
157
+ keys.each { |k| new_hash[k] = hash[k] }
158
+ hash = new_hash
159
+ end
160
+ hash
161
+ end
162
+
163
+ # Normalizes the values
164
+ def self._normalize_city(infos)
165
+ # Values
82
166
  zip_min = 100000
83
167
  zip_max = 0
84
168
  lat_min = 200
@@ -86,10 +170,8 @@ module Zipcoder
86
170
  long_min = 200
87
171
  long_max = 0
88
172
 
89
- # Get the infos associated with this city/state. While
90
- # getting those, store the min/max so we can create a
91
- # normalized version of this object
92
- infos = self.zip_info(city: city, state: state) do |info|
173
+ # Iterate through the info and get min/max of zip/lat/long
174
+ infos.each do |info|
93
175
  zip = info[:zip].to_i
94
176
  zip_min = zip if zip < zip_min
95
177
  zip_max = zip if zip > zip_max
@@ -99,40 +181,24 @@ module Zipcoder
99
181
  long_max = info[:long] if info[:long] > long_max
100
182
  end
101
183
 
184
+ # Create the normalized value
102
185
  if infos.count == 0
103
- # If there were no matches, return 0
104
- info = nil
186
+ normalized = nil
105
187
  elsif infos.count == 1
106
- # If there was 1 match, return it
107
- info = infos[0]
188
+ normalized = {
189
+ zip: infos[0][:zip],
190
+ lat: infos[0][:lat],
191
+ long: infos[0][:long],
192
+ }
108
193
  else
109
- # Create normalized object
110
- info = {
194
+ normalized = {
111
195
  zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
112
- city: city,
113
- state: state,
114
196
  lat: (lat_min+lat_max)/2,
115
197
  long: (long_min+long_max)/2
116
198
  }
117
199
  end
118
200
 
119
- # Cache the value
120
- self.city_cache[cache_key] = info if info != nil
121
-
122
- # Filter the args
123
- self._filter_hash_args info, kwargs[:keys]
124
- end
125
-
126
- # Filters arguments in return hash
127
- def self._filter_hash_args(hash, keys)
128
- return nil if hash == nil
129
-
130
- if keys != nil
131
- new_hash = {}
132
- keys.each { |k| new_hash[k] = hash[k] }
133
- hash = new_hash
134
- end
135
- hash
201
+ normalized
136
202
  end
137
203
 
138
204
  end
@@ -1,14 +1,7 @@
1
1
  require "spec_helper"
2
+ require 'benchmark'
2
3
 
3
4
  describe Zipcoder do
4
- before(:all) do
5
- described_class.load_data
6
- end
7
-
8
- before(:each) do
9
- described_class.city_cache.clear
10
- end
11
-
12
5
  it "has a version number" do
13
6
  expect(Zipcoder::VERSION).not_to be nil
14
7
  end
@@ -92,27 +85,40 @@ describe Zipcoder do
92
85
  expect(city_info[:long]).to eq(-48.87)
93
86
  end
94
87
 
95
- it "caches the unfiltered value" do
96
- expect {
97
- described_class.city_info "Austin, TX", keys: [:zip, :lat, :long]
98
- }.to change{ described_class.city_cache.values.count }.by(1)
88
+ it 'returns nil on mismatch' do
89
+ city_info = described_class.city_info "Bad City, TX"
90
+ expect(city_info).to be_nil
91
+ end
99
92
 
100
- expect {
101
- described_class.city_info "Austin, TX"
102
- }.to change{ described_class.city_cache.values.count }.by(0)
93
+ end
103
94
 
104
- city_info = described_class.city_info "Austin, TX"
105
- expect(city_info[:city]).to eq("AUSTIN")
95
+ describe "#cities" do
96
+ it "returns the cities for a state" do
97
+ cities = described_class.cities "TX"
98
+ expect(cities.count).to eq(1170)
99
+
100
+ city_info = cities[0]
101
+ expect(city_info[:city]).to eq("ABBOTT")
106
102
  expect(city_info[:state]).to eq("TX")
107
- expect(city_info[:zip]).to eq("78701-78799")
108
- expect(city_info[:lat]).to eq(30.315)
109
- expect(city_info[:long]).to eq(-48.87)
103
+ expect(city_info[:zip]).to eq("76621")
104
+ expect(city_info[:lat]).to eq(31.88)
105
+ expect(city_info[:long]).to eq(-97.07)
110
106
  end
111
- end
107
+ it "returns the cities for a state filtered" do
108
+ cities = described_class.cities "TX", keys: [:zip, :city]
109
+ expect(cities.count).to eq(1170)
112
110
 
111
+ city_info = cities[0]
112
+ expect(city_info[:city]).to eq("ABBOTT")
113
+ expect(city_info[:state]).to be_nil
114
+ expect(city_info[:zip]).to eq("76621")
115
+ expect(city_info[:lat]).to be_nil
116
+ expect(city_info[:long]).to be_nil
117
+ end
118
+ end
113
119
 
114
120
  describe("String") do
115
- describe "zip lookup" do
121
+ describe "zip_info" do
116
122
  it "returns the info for a particular zip_code" do
117
123
  zip_info = "78748".zip_info
118
124
  expect(zip_info[:city]).to eq("AUSTIN")
@@ -132,7 +138,7 @@ describe Zipcoder do
132
138
  end
133
139
  end
134
140
 
135
- describe "city lookup" do
141
+ describe "city_info" do
136
142
  it "returns the info for a particular city" do
137
143
  city_info = "Austin, TX".city_info
138
144
  expect(city_info[:city]).to eq("AUSTIN")
@@ -150,6 +156,31 @@ describe Zipcoder do
150
156
  expect(city_info[:long]).to be_nil
151
157
  end
152
158
  end
159
+
160
+ describe "cities" do
161
+ it "returns the cities for a state" do
162
+ cities = "TX".cities
163
+ expect(cities.count).to eq(1170)
164
+
165
+ city_info = cities[0]
166
+ expect(city_info[:city]).to eq("ABBOTT")
167
+ expect(city_info[:state]).to eq("TX")
168
+ expect(city_info[:zip]).to eq("76621")
169
+ expect(city_info[:lat]).to eq(31.88)
170
+ expect(city_info[:long]).to eq(-97.07)
171
+ end
172
+ it "returns the cities for a state filtered" do
173
+ cities = "TX".cities keys: [:zip, :city]
174
+ expect(cities.count).to eq(1170)
175
+
176
+ city_info = cities[0]
177
+ expect(city_info[:city]).to eq("ABBOTT")
178
+ expect(city_info[:state]).to be_nil
179
+ expect(city_info[:zip]).to eq("76621")
180
+ expect(city_info[:lat]).to be_nil
181
+ expect(city_info[:long]).to be_nil
182
+ end
183
+ end
153
184
  end
154
185
 
155
186
  describe("Integer") do
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapman