zipcoder 0.1.0 → 0.2.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.
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