postal_code 0.0.1
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/README.rdoc +140 -0
- data/Rakefile +18 -0
- data/db/us.csv +43628 -0
- data/lib/postal_code.rb +78 -0
- data/lib/postal_code/server.rb +56 -0
- data/postal_code.gemspec +12 -0
- data/test/data/us.csv +4 -0
- data/test/test_postal_code.rb +87 -0
- metadata +54 -0
data/lib/postal_code.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module PostalCode
|
2
|
+
|
3
|
+
VERSION = '0.0.1'
|
4
|
+
|
5
|
+
CityOffset = 0
|
6
|
+
StateOffset = 1
|
7
|
+
DB = File.join File.dirname(__FILE__), '..', 'db', 'us.csv'
|
8
|
+
FS = "\t" # field separator
|
9
|
+
RS = "\n" # record separator
|
10
|
+
|
11
|
+
@cache = {}
|
12
|
+
|
13
|
+
#
|
14
|
+
# Load the entire postal code database into memory.
|
15
|
+
#
|
16
|
+
def self.load_cache
|
17
|
+
open(DB) do |db|
|
18
|
+
db.each_line do |line|
|
19
|
+
pcode, city, state = line.rstrip.split(FS)
|
20
|
+
@cache[pcode] = [city, state]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Clear the in-memory postal code cache.
|
27
|
+
#
|
28
|
+
def self.clear_cache
|
29
|
+
@cache.clear
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Return the city for the given postal code.
|
34
|
+
#
|
35
|
+
def self.city postal_code
|
36
|
+
fetch(postal_code)[CityOffset]
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Return the state (two-letter abbreviation) for the given postal code.
|
41
|
+
#
|
42
|
+
def self.state postal_code
|
43
|
+
fetch(postal_code)[StateOffset]
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# * All postal code parameters must be strings.
|
48
|
+
# * Postal codes are not calculable, therefore should not be numeric.
|
49
|
+
# * A postal code with a leading zero would be an octal. However, *08880* is
|
50
|
+
# an invalid octal numeric, yet a valid postal code.
|
51
|
+
#
|
52
|
+
def self.valid_format? postal_code
|
53
|
+
postal_code =~ /^\d{5}(-\d{4})?$/
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Fetch postal code record.
|
58
|
+
#
|
59
|
+
def self.fetch postal_code
|
60
|
+
return [nil, nil] unless valid_format? postal_code
|
61
|
+
pcode = postal_code[0..4] # use first 5 characters; ignore ZIP+4 extension
|
62
|
+
|
63
|
+
if @cache.has_key? pcode
|
64
|
+
@cache[pcode]
|
65
|
+
else
|
66
|
+
records = %x[grep "^#{pcode}#{FS}" #{DB}].split(RS)
|
67
|
+
|
68
|
+
if records.empty?
|
69
|
+
[nil, nil]
|
70
|
+
else
|
71
|
+
@cache[pcode] = records.last.split(FS)[1..2]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private_class_method :fetch, :valid_format?
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Postal Code resolution service for network clients.
|
3
|
+
#
|
4
|
+
# Could implement a DRb and TCP/UDP server.
|
5
|
+
#
|
6
|
+
# Requires in user code would look like this:
|
7
|
+
#
|
8
|
+
# require 'postal_code/tcpserver'
|
9
|
+
#
|
10
|
+
# or
|
11
|
+
#
|
12
|
+
# require 'postal_code/drbserver'
|
13
|
+
#
|
14
|
+
|
15
|
+
__END__
|
16
|
+
|
17
|
+
require_relative '../postal_code'
|
18
|
+
|
19
|
+
# DRb
|
20
|
+
require 'drb'
|
21
|
+
|
22
|
+
$SAFE = 1 # disable eval() and friends
|
23
|
+
URI = "druby://127.0.0.1:8787"
|
24
|
+
|
25
|
+
class PostalCode::DRbServer
|
26
|
+
|
27
|
+
def initialize host, port
|
28
|
+
end
|
29
|
+
|
30
|
+
def postal_code code
|
31
|
+
# could return more than one line
|
32
|
+
#zip, city, state = %x[grep ^#{code} #{DB}].split("\t")
|
33
|
+
#Time.now
|
34
|
+
#"#{zip},#{city},#{state}"
|
35
|
+
code
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
DRb.start_service(URI, PCode.new)
|
41
|
+
DRb.thread.join
|
42
|
+
|
43
|
+
|
44
|
+
__END__
|
45
|
+
|
46
|
+
# TCP
|
47
|
+
require 'socket'
|
48
|
+
|
49
|
+
server = TCPServer.new('127.0.0.1', 5100)
|
50
|
+
loop do
|
51
|
+
client = server.accept
|
52
|
+
client.gets
|
53
|
+
client.puts 'Hello!'
|
54
|
+
client.puts "Time is #{Time.now}"
|
55
|
+
client.close
|
56
|
+
end
|
data/postal_code.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new('postal_code') do |s|
|
2
|
+
s.version = File.read('lib/postal_code.rb')[/VERSION = '(.*)'/, 1]
|
3
|
+
s.author = 'Clint Pachl'
|
4
|
+
s.email = 'pachl@ecentryx.com'
|
5
|
+
s.homepage = 'http://ecentryx.com/gems/postal_code'
|
6
|
+
s.license = 'ISC'
|
7
|
+
s.summary = 'Postal Code Resolver'
|
8
|
+
s.description = 'Resolves a postal code to a corresponding city or state.'
|
9
|
+
s.files = Dir['README.rdoc', 'Rakefile', 'postal_code.gemspec',
|
10
|
+
'lib/**/*.rb', 'test/**/*', 'db/*.csv']
|
11
|
+
s.test_files = Dir['test/*.rb']
|
12
|
+
end
|
data/test/data/us.csv
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
gem 'minitest' # minitest in 1.9 stdlib is crufty
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'postal_code'
|
4
|
+
|
5
|
+
class PostalCodeTest < Minitest::Test
|
6
|
+
|
7
|
+
ProductionDB = PostalCode::DB.dup
|
8
|
+
TestDB = File.dirname(__FILE__) + '/data/us.csv'
|
9
|
+
PostalCode::DB.replace(TestDB)
|
10
|
+
|
11
|
+
def test_database_format
|
12
|
+
[ProductionDB, TestDB].each do |db|
|
13
|
+
[ %x[head -n1 #{db}],
|
14
|
+
%x[tail -n1 #{db}]
|
15
|
+
].each do |tuple|
|
16
|
+
assert_match(/^\d{5}\t.+\t[A-Z]{2}$/, tuple)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_postal_code_with_leading_zeros
|
22
|
+
pcode = '00001'
|
23
|
+
assert_equal 'City1', PostalCode.city(pcode)
|
24
|
+
assert_equal 'AA', PostalCode.state(pcode)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_postal_code_with_plus_4_extension
|
28
|
+
pcode = '00001-1234'
|
29
|
+
assert_equal 'City1', PostalCode.city(pcode)
|
30
|
+
assert_equal 'AA', PostalCode.state(pcode)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_postal_code_found_in_single_city
|
34
|
+
pcode = '10000'
|
35
|
+
assert_equal 'City2', PostalCode.city(pcode)
|
36
|
+
assert_equal 'BA', PostalCode.state(pcode)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_postal_code_found_in_multiple_cities
|
40
|
+
pcode = '20000'
|
41
|
+
assert_equal 'City4', PostalCode.city(pcode)
|
42
|
+
assert_equal 'CA', PostalCode.state(pcode)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_postal_code_not_found_in_any_city
|
46
|
+
pcode = '99999'
|
47
|
+
assert_equal nil, PostalCode.city(pcode)
|
48
|
+
assert_equal nil, PostalCode.state(pcode)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_reactive_cache
|
52
|
+
PostalCode.clear_cache
|
53
|
+
assert_equal 0, PostalCode.instance_variable_get(:@cache).size
|
54
|
+
PostalCode.city('00001') # hit
|
55
|
+
assert_equal 1, PostalCode.instance_variable_get(:@cache).size
|
56
|
+
|
57
|
+
PostalCode.clear_cache
|
58
|
+
assert_equal 0, PostalCode.instance_variable_get(:@cache).size
|
59
|
+
PostalCode.city('99999') # miss
|
60
|
+
assert_equal 0, PostalCode.instance_variable_get(:@cache).size
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_proactive_cache
|
64
|
+
tuple_cnt = open(PostalCode::DB) {|db| db.readlines.size}
|
65
|
+
duplicate_cnt = 1
|
66
|
+
uniq_tuple_cnt = tuple_cnt - duplicate_cnt
|
67
|
+
|
68
|
+
assert_equal 4, tuple_cnt
|
69
|
+
PostalCode.clear_cache
|
70
|
+
assert_equal 0, PostalCode.instance_variable_get(:@cache).size
|
71
|
+
PostalCode.load_cache
|
72
|
+
assert_equal uniq_tuple_cnt, PostalCode.instance_variable_get(:@cache).size
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_postal_code_validations
|
76
|
+
refute PostalCode.send(:valid_format?, '99999').nil?
|
77
|
+
refute PostalCode.send(:valid_format?, '00001').nil?
|
78
|
+
refute PostalCode.send(:valid_format?, '00001-1234').nil?
|
79
|
+
assert PostalCode.send(:valid_format?, '00001-123').nil?
|
80
|
+
assert PostalCode.send(:valid_format?, '00001-').nil?
|
81
|
+
assert PostalCode.send(:valid_format?, 99999).nil?
|
82
|
+
assert PostalCode.send(:valid_format?, 00001).nil?
|
83
|
+
assert PostalCode.send(:valid_format?, '1').nil?
|
84
|
+
assert PostalCode.send(:valid_format?, 1).nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: postal_code
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Clint Pachl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Resolves a postal code to a corresponding city or state.
|
15
|
+
email: pachl@ecentryx.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.rdoc
|
21
|
+
- Rakefile
|
22
|
+
- postal_code.gemspec
|
23
|
+
- lib/postal_code/server.rb
|
24
|
+
- lib/postal_code.rb
|
25
|
+
- test/test_postal_code.rb
|
26
|
+
- test/data/us.csv
|
27
|
+
- db/us.csv
|
28
|
+
homepage: http://ecentryx.com/gems/postal_code
|
29
|
+
licenses:
|
30
|
+
- ISC
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.23
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Postal Code Resolver
|
53
|
+
test_files:
|
54
|
+
- test/test_postal_code.rb
|