postal_code 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ 00001 City1 AA
2
+ 10000 City2 BA
3
+ 20000 City3 CA
4
+ 20000 City4 CA
@@ -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