zipcoder 0.4.2 → 0.5.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: 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