zipcoder 0.1.0 → 0.2.0

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