zipcoder 0.4.2 → 0.5.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: 553ed39e32f59b79a062775295679d51ac2344e4
4
- data.tar.gz: a0a184068d50d7846f20c3a774d19d3518f65222
3
+ metadata.gz: 6acfa10df7bcb97c1d077f57b44eb8f07db3ff49
4
+ data.tar.gz: 466550b2bbf85a28a1b67381fb6408c71d3b8fcd
5
5
  SHA512:
6
- metadata.gz: d9072580bc9aef886a9e2a9301f777df3173ac9b73c05fd71e4afb08af39413002e646eb9731e949e7d648a120d04ec876d733b121e089f28b700623bfe867b9
7
- data.tar.gz: 228d4c0374c609c44937bcdd8b2348129c3546d697e3ce5f03316fa3a767013c3328e3ede3f7b3f2e4b7613043c21c8b913c245cab226a7d77faceb11ce098d8
6
+ metadata.gz: 268af7c1b49cd0528cc2caae8d040ee64127063d5cb292f4a26ed62b76e3f910785f74c77425b05466fd0c0ee9e591d7e66d7e26293288d43ac15e4ce2fd4ca7
7
+ data.tar.gz: f2ba366f03a0ec93c31d62b17fb8ab09ddd274c0e53be3bf1ee3deba1ceecfbf581818faea8f6bba17c608a11baa8e13211a67efdbfbfc5eba399e8d0da284d8
data/README.md CHANGED
@@ -7,6 +7,8 @@ Gem for performing zip code lookup operations
7
7
 
8
8
  ## Revision History
9
9
 
10
+ - v0.5.0:
11
+ - added Redis backend support
10
12
  - v0.4.2:
11
13
  - updated paths to fix RAILS require issue
12
14
  - v0.4.1:
@@ -64,6 +66,33 @@ This will immediately load the data structures. Currently it takes roughly 3s
64
66
  to create and import all of the data structures. I will look at ways to
65
67
  reduce this later.
66
68
 
69
+ #### Redis Support
70
+
71
+ To use Redis as the cache for zipcoder rather than memory, you must do the
72
+ following
73
+
74
+ **install the 'redis' Gem (or add to your Gemfile if using Rails):**
75
+
76
+ ```ruby
77
+ gme 'redis'
78
+ ```
79
+
80
+ *Note that this Gem supports Ruby >= 2.0 so I could NOT use the latest Redis
81
+ version. I had to use v3.3.5 to test.*
82
+
83
+ **create a redis cacher and pass it to the "load_cache" method:**
84
+
85
+ ```ruby
86
+ require 'zipcoder'
87
+ require 'zipcoder/cacher/redis'
88
+
89
+ cacher = Zipcoder::Cacher::Redis.new(**args)
90
+ Zipcoder.load_cache(cacher)
91
+ ```
92
+
93
+ Please check the [here](https://github.com/redis/redis-rb) for the different
94
+ options to use when instantiating the "Redis" client.
95
+
67
96
  ### Methods
68
97
 
69
98
  The library overrides the String (and in some instances Integer) class to
@@ -1,111 +1,109 @@
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
1
+ module Zipcoder
2
+ module Cacher
3
+ # The cacher base class places all of the objects in memory. The
4
+ # abstraction will later allow us to override for MemCacher and
5
+ # Redis implementations
6
+ class Base
7
+ KEY_BASE = "zipcoder"
8
+ KEY_ZIP = "#{KEY_BASE}:zip"
9
+ KEY_CITY = "#{KEY_BASE}:city"
10
+ KEY_STATE = "#{KEY_BASE}:state"
11
+ KEY_STATES = "#{KEY_BASE}:states"
12
+
13
+ #region Override These
14
+ def _init_cache(**kwargs)
15
+ # Override ME
16
+ end
11
17
 
12
- def _empty_cache
13
- @cache.clear
14
- end
18
+ def _empty_cache
19
+ # Override ME
20
+ end
15
21
 
16
- def _write_cache(key, value)
17
- @cache[key] = value
18
- end
22
+ def _write_cache(key, value)
23
+ # Override ME
24
+ end
19
25
 
20
- def _read_cache(key)
21
- @cache[key]
22
- end
26
+ def _read_cache(key)
27
+ # Override ME
28
+ end
23
29
 
24
- def _iterate_keys(**kwargs, &block)
25
- return if block == nil
30
+ def _iterate_keys(**kwargs, &block)
31
+ # Override ME
32
+ end
26
33
 
27
- start_with = kwargs[:start_with]
34
+ #endregion
28
35
 
29
- @cache.keys.each do |key|
30
- if start_with == nil or key.start_with?(start_with)
31
- block.call(key)
32
- end
36
+ def initialize(**kwargs)
37
+ self._init_cache **kwargs
33
38
  end
34
- end
35
39
 
36
- #endregion
40
+ def load
41
+ this_dir = File.expand_path(File.dirname(__FILE__))
37
42
 
38
- def initialize(**kwargs)
39
- self._init_cache **kwargs
40
- end
43
+ # Load zip cache from file
44
+ zip_data = File.join(this_dir, '..', '..', 'data', 'zip_data.yml')
45
+ zip_codes = YAML.load(File.open(zip_data))
41
46
 
42
- def load
43
- this_dir = File.expand_path(File.dirname(__FILE__))
47
+ # Initialize
48
+ _empty_cache
49
+ city_states = {}
50
+ state_lookup = {}
44
51
 
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))
52
+ # Add the zip codes to the cache
53
+ zip_codes.each do |zip, info|
54
+ city = _capitalize_all(info[:city])
55
+ info[:city] = city
56
+ state = info[:state]
48
57
 
49
- # Initialize
50
- _empty_cache
51
- city_states = {}
52
- state_lookup = {}
58
+ # Iterate through the zip codes and add them to the zip cache
59
+ _write_cache _zip_cache(zip), info
53
60
 
54
- # Add the zip codes to the cache
55
- zip_codes.each do |zip, info|
56
- city = _capitalize_all(info[:city])
57
- info[:city] = city
58
- state = info[:state]
61
+ # Create the city lookups
62
+ city_state = "#{city.upcase},#{state.upcase}"
63
+ infos = city_states[city_state] || []
64
+ infos << info
65
+ city_states[city_state] = infos
66
+ end
59
67
 
60
- # Iterate through the zip codes and add them to the zip cache
61
- _write_cache _zip_cache(zip), info
68
+ # Normalize each city and populate the state cache
69
+ city_states.each do |city_state, infos|
70
+ city = infos[0][:city]
71
+ state = infos[0][:state]
62
72
 
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
73
+ # Populate the City Cache
74
+ normalized = _normalize_city(infos)
75
+ _write_cache _city_cache(city_state), normalized
69
76
 
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]
77
+ # Populate the State Cache
78
+ cities = state_lookup[state] || []
79
+ cities << city
80
+ state_lookup[state] = cities
81
+ end
74
82
 
75
- # Populate the City Cache
76
- normalized = _normalize_city(infos)
77
- _write_cache _city_cache(city_state), normalized
83
+ # Set the cities cache
84
+ state_lookup.each do |state, cities|
85
+ _write_cache _state_cache(state), cities.sort
86
+ end
78
87
 
79
- # Populate the State Cache
80
- cities = state_lookup[state] || []
81
- cities << city
82
- state_lookup[state] = cities
88
+ # Set the states cache
89
+ self._write_cache _states, state_lookup.keys.sort
83
90
  end
84
91
 
85
- # Set the cities cache
86
- state_lookup.each do |state, cities|
87
- _write_cache _state_cache(state), cities.sort
92
+ def read_zip_cache(zip)
93
+ _read_cache _zip_cache(zip)
88
94
  end
89
95
 
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
96
+ def read_city_cache(city_state)
97
+ _read_cache _city_cache(city_state)
98
+ end
101
99
 
102
- def read_state_cache(state)
103
- _read_cache _state_cache(state)
104
- end
100
+ def read_state_cache(state)
101
+ _read_cache _state_cache(state)
102
+ end
105
103
 
106
- def read_states
107
- _read_cache _states
108
- end
104
+ def read_states
105
+ _read_cache _states
106
+ end
109
107
 
110
108
  def iterate_zips(&block)
111
109
  return if block == nil
@@ -123,72 +121,73 @@ module Cacher
123
121
  end
124
122
  end
125
123
 
126
- private
124
+ private
127
125
 
128
- def _zip_cache(zip)
129
- "zipcoder:zip:#{zip}"
130
- end
126
+ def _zip_cache(zip)
127
+ "#{KEY_ZIP}:#{zip}"
128
+ end
131
129
 
132
- def _city_cache(city_state)
133
- "zipcoder:city:#{city_state}"
134
- end
130
+ def _city_cache(city_state)
131
+ "#{KEY_CITY}:#{city_state}"
132
+ end
135
133
 
136
- def _state_cache(state)
137
- "zipcoder:state:#{state}"
138
- end
134
+ def _state_cache(state)
135
+ "#{KEY_STATE}:#{state}"
136
+ end
139
137
 
140
- def _states
141
- "zipcoder:states"
142
- end
138
+ def _states
139
+ KEY_STATES
140
+ end
143
141
 
144
- def _capitalize_all(string)
145
- string.split(' ').map {|w| w.capitalize }.join(' ')
146
- end
142
+ def _capitalize_all(string)
143
+ string.split(' ').map {|w| w.capitalize }.join(' ')
144
+ end
147
145
 
148
- # Normalizes the values
149
- def _normalize_city(infos)
150
- # Values
151
- zip_min = 100000
152
- zip_max = 0
153
- lat_min = 200
154
- lat_max = -200
155
- long_min = 200
156
- long_max = -200
157
-
158
- # Iterate through the info and get min/max of zip/lat/long
159
- infos.each do |info|
160
- zip = info[:zip].to_i
161
- zip_min = zip if zip < zip_min
162
- zip_max = zip if zip > zip_max
163
- lat_min = info[:lat] if info[:lat] < lat_min
164
- lat_max = info[:lat] if info[:lat] > lat_max
165
- long_min = info[:long] if info[:long] < long_min
166
- long_max = info[:long] if info[:long] > long_max
167
- end
168
-
169
- # Create the normalized value
170
- if infos.count == 0
171
- normalized = nil
172
- elsif infos.count == 1
173
- normalized = {
174
- city: infos[0][:city],
175
- state: infos[0][:state],
176
- zip: infos[0][:zip],
177
- lat: infos[0][:lat],
178
- long: infos[0][:long],
179
- }
180
- else
181
- normalized = {
182
- city: infos[0][:city],
183
- state: infos[0][:state],
184
- zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
185
- lat: ((lat_min+lat_max)/2).round(4),
186
- long: ((long_min+long_max)/2).round(4)
187
- }
188
- end
189
-
190
- normalized
191
- end
146
+ # Normalizes the values
147
+ def _normalize_city(infos)
148
+ # Values
149
+ zip_min = 100000
150
+ zip_max = 0
151
+ lat_min = 200
152
+ lat_max = -200
153
+ long_min = 200
154
+ long_max = -200
155
+
156
+ # Iterate through the info and get min/max of zip/lat/long
157
+ infos.each do |info|
158
+ zip = info[:zip].to_i
159
+ zip_min = zip if zip < zip_min
160
+ zip_max = zip if zip > zip_max
161
+ lat_min = info[:lat] if info[:lat] < lat_min
162
+ lat_max = info[:lat] if info[:lat] > lat_max
163
+ long_min = info[:long] if info[:long] < long_min
164
+ long_max = info[:long] if info[:long] > long_max
165
+ end
192
166
 
167
+ # Create the normalized value
168
+ if infos.count == 0
169
+ normalized = nil
170
+ elsif infos.count == 1
171
+ normalized = {
172
+ city: infos[0][:city],
173
+ state: infos[0][:state],
174
+ zip: infos[0][:zip],
175
+ lat: infos[0][:lat],
176
+ long: infos[0][:long],
177
+ }
178
+ else
179
+ normalized = {
180
+ city: infos[0][:city],
181
+ state: infos[0][:state],
182
+ zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
183
+ lat: ((lat_min+lat_max)/2).round(4),
184
+ long: ((long_min+long_max)/2).round(4)
185
+ }
186
+ end
187
+
188
+ normalized
189
+ end
190
+
191
+ end
193
192
  end
194
- end
193
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'base'
2
+
3
+ module Zipcoder
4
+ module Cacher
5
+ class Memory < Base
6
+ def _init_cache(**kwargs)
7
+ @cache = {}
8
+ end
9
+
10
+ def _empty_cache
11
+ @cache.clear
12
+ end
13
+
14
+ def _write_cache(key, value)
15
+ @cache[key] = value
16
+ end
17
+
18
+ def _read_cache(key)
19
+ @cache[key]
20
+ end
21
+
22
+ def _iterate_keys(**kwargs, &block)
23
+ return if block == nil
24
+
25
+ start_with = kwargs[:start_with]
26
+
27
+ @cache.keys.each do |key|
28
+ if start_with == nil or key.start_with?(start_with)
29
+ block.call(key)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ require_relative 'base'
2
+ require 'redis'
3
+ require 'json'
4
+
5
+ module Zipcoder
6
+ module Cacher
7
+ class Redis < Base
8
+
9
+ # This is here for stubbing
10
+ def self._create_redis_client(**kwargs)
11
+ ::Redis.new(**kwargs)
12
+ end
13
+
14
+ def _init_cache(**kwargs)
15
+ @redis = self.class._create_redis_client(**kwargs)
16
+ end
17
+
18
+ def _empty_cache
19
+ keys = @redis.keys("#{KEY_BASE}*")
20
+ @redis.del(*keys) unless keys.empty?
21
+ end
22
+
23
+ def _write_cache(key, value)
24
+ return if value == nil
25
+ @redis.set(key, value.to_json)
26
+ end
27
+
28
+ def _read_cache(key)
29
+ data = @redis.get(key)
30
+ data == nil ? nil : JSON.parse(data, :symbolize_names => true)
31
+ end
32
+
33
+ def _iterate_keys(**kwargs, &block)
34
+ return if block == nil
35
+
36
+ start_with = kwargs[:start_with] || KEY_BASE
37
+
38
+ # Redis "keys" command will pre-filter the keys for us so no
39
+ # need for "if" statement
40
+ @redis.keys("#{start_with}*").each do |key|
41
+ block.call(key)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module Zipcoder
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/zipcoder.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "zipcoder/version"
2
- require "zipcoder/cacher/base"
2
+ require "zipcoder/cacher/memory"
3
3
  require "zipcoder/ext/string"
4
4
  require "zipcoder/ext/integer"
5
5
  require "yaml"
@@ -19,7 +19,7 @@ module Zipcoder
19
19
 
20
20
  # Loads the data into memory
21
21
  def self.load_cache(cacher=nil)
22
- @@cacher = cacher || Cacher::Base.new
22
+ @@cacher = cacher || Cacher::Memory.new
23
23
  self.cacher.load
24
24
  end
25
25
 
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+ require "zipcoder/cacher/redis"
3
+
4
+
5
+ describe Zipcoder::Cacher::Redis do
6
+ stub_redis_once = false
7
+
8
+ before(:each) do
9
+ unless stub_redis_once
10
+ allow(Zipcoder::Cacher::Redis).to receive(:_create_redis_client) do
11
+ RedisStub.new
12
+ end
13
+ Zipcoder.load_cache Zipcoder::Cacher::Redis.new
14
+ end
15
+
16
+ stub_redis_once = true
17
+ end
18
+
19
+ describe "#zip_info" do
20
+ it "match" do
21
+ info = "78748".zip_info
22
+ expect(info[:city]).to eq("Austin")
23
+ end
24
+
25
+ it "matches city" do
26
+ zips = Zipcoder.zip_info city: "Austin", state: "TX"
27
+ expect(zips.count).to eq(47)
28
+ end
29
+
30
+ it "no match" do
31
+ info = "78706".zip_info
32
+ expect(info).to be_nil
33
+ end
34
+ end
35
+
36
+ describe "#zip_cities" do
37
+ it "match" do
38
+ cities = "78748".zip_cities
39
+ expect(cities.count).to eq(1)
40
+ expect(cities[0][:city]).to eq("Austin")
41
+ end
42
+
43
+ it "no match" do
44
+ cities = "78706".zip_cities
45
+ expect(cities.count).to eq(0)
46
+ end
47
+ end
48
+ end
data/spec/spec_helper.rb CHANGED
@@ -9,3 +9,59 @@ end
9
9
  $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
10
10
  require "zipcoder"
11
11
 
12
+ class RedisStub
13
+
14
+ def initialize
15
+ @cache = {}
16
+ end
17
+
18
+ def set(key, value)
19
+ @cache[key] = value
20
+ end
21
+
22
+ def get(key)
23
+ @cache[key]
24
+ end
25
+
26
+ def keys(filter=nil)
27
+ if filter == nil
28
+ @cache.keys
29
+ else
30
+ wildcard_start = false
31
+ wildcard_end = false
32
+
33
+ if filter.start_with? '*'
34
+ wildcard_start = true
35
+ filter = filter[1..-1]
36
+ end
37
+
38
+ if filter.end_with? '*'
39
+ wildcard_end = true
40
+ filter = filter[0..-2]
41
+ end
42
+
43
+ keys = []
44
+ @cache.keys.each do |key|
45
+ if wildcard_start and wildcard_end
46
+ keys << key if key.include? filter
47
+ elsif wildcard_start
48
+ keys << key if key.end_with? filter
49
+ elsif wildcard_end
50
+ keys << key if key.start_with? filter
51
+ elsif filter == key
52
+ keys << key
53
+ end
54
+ end
55
+
56
+ keys
57
+ end
58
+ end
59
+
60
+ def del(*keys)
61
+ keys.each do |key|
62
+ @cache.delete(key)
63
+ end
64
+ end
65
+
66
+ end
67
+
@@ -1,7 +1,10 @@
1
1
  require "spec_helper"
2
- require 'benchmark'
3
2
 
4
3
  describe Zipcoder do
4
+ before(:all) do
5
+ Zipcoder.load_cache
6
+ end
7
+
5
8
  it "has a version number" do
6
9
  expect(Zipcoder::VERSION).not_to be nil
7
10
  end
data/zipcoder.gemspec CHANGED
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'rspec', '>= 3.5.0'
25
25
  spec.add_development_dependency 'simplecov'
26
26
  spec.add_development_dependency 'codecov'
27
+ spec.add_development_dependency 'redis', "~> 3.3.5"
27
28
  spec.required_ruby_version = '>= 2.0'
28
29
  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.4.2
4
+ version: 0.5.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-11 00:00:00.000000000 Z
11
+ date: 2018-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: redis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 3.3.5
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 3.3.5
83
97
  description:
84
98
  email:
85
99
  - eric.chappy@gmail.com
@@ -103,9 +117,12 @@ files:
103
117
  - lib/data/zipcode.csv
104
118
  - lib/zipcoder.rb
105
119
  - lib/zipcoder/cacher/base.rb
120
+ - lib/zipcoder/cacher/memory.rb
121
+ - lib/zipcoder/cacher/redis.rb
106
122
  - lib/zipcoder/ext/integer.rb
107
123
  - lib/zipcoder/ext/string.rb
108
124
  - lib/zipcoder/version.rb
125
+ - spec/cacher_redis_spec.rb
109
126
  - spec/spec_helper.rb
110
127
  - spec/zipcoder_spec.rb
111
128
  - zipcoder.gemspec
@@ -134,5 +151,6 @@ signing_key:
134
151
  specification_version: 4
135
152
  summary: Converts zip codes to cities, lat/long, and vice-versa
136
153
  test_files:
154
+ - spec/cacher_redis_spec.rb
137
155
  - spec/spec_helper.rb
138
156
  - spec/zipcoder_spec.rb