auspost 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.bnsignore +18 -0
- data/History.txt +4 -0
- data/README.txt +73 -0
- data/Rakefile +22 -0
- data/bin/auspost +7 -0
- data/lib/auspost.rb +48 -0
- data/lib/auspost/active_record.rb +5 -0
- data/lib/auspost/active_record/validations.rb +82 -0
- data/lib/auspost/postie.rb +172 -0
- data/spec/auspost/active_record/validations_spec.rb +72 -0
- data/spec/auspost/postie_spec.rb +55 -0
- data/spec/fixtures/auspost.html +187 -0
- data/spec/fixtures/postal_worker.rb +39 -0
- data/spec/spec_helper.rb +15 -0
- metadata +83 -0
data/.bnsignore
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# Bones {
|
8
|
+
# ignore_file '.gitignore'
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# For a project with a C extension, the following would be a good set of
|
12
|
+
# exclude patterns (uncomment them if you want to use them):
|
13
|
+
# *.[oa]
|
14
|
+
# *~
|
15
|
+
announcement.txt
|
16
|
+
coverage
|
17
|
+
doc
|
18
|
+
pkg
|
data/History.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
auspost
|
2
|
+
by Cameron Barrie
|
3
|
+
http://wiki.github.com/whalec/auspost
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Give you the ability to search the Australia Post Web site with either Suburb or Postcode, and return an array of Locations.
|
8
|
+
Also add's validation methods to ActiveRecord Objects to allow you to validate that A Postcode + State + Suburb is correct
|
9
|
+
|
10
|
+
== FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
ActiveRecord Validations - validates_location
|
13
|
+
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
class Foo < ActiveRecord::Base
|
18
|
+
include Auspost::Postie
|
19
|
+
|
20
|
+
before_save :check_address
|
21
|
+
|
22
|
+
def check_address
|
23
|
+
location?(:postcode => 2038, :suburb => "Annandale", :state => "NSW")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
or
|
28
|
+
|
29
|
+
require 'auspost/active_record'
|
30
|
+
class Foo < ActiveRecord::Base
|
31
|
+
|
32
|
+
validate_location
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
== REQUIREMENTS:
|
37
|
+
|
38
|
+
Nokogiri - For teh XML parsing.
|
39
|
+
|
40
|
+
== INSTALL:
|
41
|
+
|
42
|
+
sudo gem install auspost
|
43
|
+
|
44
|
+
== TODO:
|
45
|
+
|
46
|
+
Validations for Active Record should take more options, including mapping the suburb, postcode and state to other columns
|
47
|
+
|
48
|
+
validate_location :suburb => :home, :state => "state.name", :on => [:create, :change], :if => lambda { !home.blank? && !postcode.blank? }
|
49
|
+
|
50
|
+
== LICENSE:
|
51
|
+
|
52
|
+
(The MIT License)
|
53
|
+
|
54
|
+
Copyright (c) 2009
|
55
|
+
|
56
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
57
|
+
a copy of this software and associated documentation files (the
|
58
|
+
'Software'), to deal in the Software without restriction, including
|
59
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
60
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
61
|
+
permit persons to whom the Software is furnished to do so, subject to
|
62
|
+
the following conditions:
|
63
|
+
|
64
|
+
The above copyright notice and this permission notice shall be
|
65
|
+
included in all copies or substantial portions of the Software.
|
66
|
+
|
67
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
68
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
69
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
70
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
71
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
72
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
73
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
require 'bones'
|
3
|
+
rescue LoadError
|
4
|
+
abort '### Please install the "bones" gem ###'
|
5
|
+
end
|
6
|
+
|
7
|
+
ensure_in_path 'lib'
|
8
|
+
require 'auspost'
|
9
|
+
|
10
|
+
task :default => 'test:run'
|
11
|
+
task 'gem:release' => 'test:run'
|
12
|
+
|
13
|
+
Bones {
|
14
|
+
name 'auspost'
|
15
|
+
authors 'Cameron Barrie'
|
16
|
+
email 'camwritescode@gmail.com'
|
17
|
+
dependencies ['nokogiri', "memcache-client"]
|
18
|
+
url 'http://www.camwritescode.com/auspost'
|
19
|
+
version Auspost::VERSION
|
20
|
+
}
|
21
|
+
|
22
|
+
# EOF
|
data/bin/auspost
ADDED
data/lib/auspost.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Auspost
|
2
|
+
|
3
|
+
# :stopdoc:
|
4
|
+
VERSION = '0.8.5'
|
5
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
6
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
7
|
+
# :startdoc:
|
8
|
+
|
9
|
+
# Returns the version string for the library.
|
10
|
+
#
|
11
|
+
def self.version
|
12
|
+
VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the library path for the module. If any arguments are given,
|
16
|
+
# they will be joined to the end of the libray path using
|
17
|
+
# <tt>File.join</tt>.
|
18
|
+
#
|
19
|
+
def self.libpath( *args )
|
20
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the lpath for the module. If any arguments are given,
|
24
|
+
# they will be joined to the end of the path using
|
25
|
+
# <tt>File.join</tt>.
|
26
|
+
#
|
27
|
+
def self.path( *args )
|
28
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Utility method used to require all files ending in .rb that lie in the
|
32
|
+
# directory below this file that has the same name as the filename passed
|
33
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
34
|
+
# the _filename_ does not have to be equivalent to the directory.
|
35
|
+
#
|
36
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
37
|
+
dir ||= ::File.basename(fname, '.*')
|
38
|
+
search_me = ::File.expand_path(
|
39
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
40
|
+
|
41
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
42
|
+
end
|
43
|
+
|
44
|
+
end # module Auspost
|
45
|
+
require 'open-uri'
|
46
|
+
require 'nokogiri'
|
47
|
+
require 'memcache'
|
48
|
+
require 'auspost/postie'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Auspost
|
2
|
+
module Postie
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module Validations
|
6
|
+
|
7
|
+
@@valid_attributes = {
|
8
|
+
:state => {:accessor => :state, :message => ["%s is not found in the %s postcode", :state, :postcode] },
|
9
|
+
:postcode => {:accessor => :postcode, :message => ["%s cannot be found", :postcode] },
|
10
|
+
:suburb => {:accessor => :suburb, :message => ["%s is not found in the %s postcode", :suburb, :postcode] },
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def validate_location
|
19
|
+
result = location?(map_attributes)
|
20
|
+
if result && !result.status
|
21
|
+
result.errors.each do |error|
|
22
|
+
message = @@valid_attributes[error.accessor][:message]
|
23
|
+
if message.is_a?(String)
|
24
|
+
errors.add(error.accessor, message)
|
25
|
+
else
|
26
|
+
mappings = message[1..-1].map do |x|
|
27
|
+
if x.to_s.include?(".")
|
28
|
+
methods = x.split(".")
|
29
|
+
send(methods.first).send(methods.last).to_s.upcase
|
30
|
+
else
|
31
|
+
send(x).to_s.upcase
|
32
|
+
end
|
33
|
+
end
|
34
|
+
errors.add(error.accessor, message.first % mappings)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_attributes
|
41
|
+
@@valid_attributes
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def map_attributes
|
47
|
+
{
|
48
|
+
:state => get_column(:state),
|
49
|
+
:postcode => get_column(:postcode),
|
50
|
+
:suburb => get_column(:suburb)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_column(column)
|
55
|
+
if @@valid_attributes[column][:accessor].nil?
|
56
|
+
column
|
57
|
+
elsif @@valid_attributes[column][:accessor].is_a?(String) && @@valid_attributes[column][:accessor].include?(".")
|
58
|
+
association = @@valid_attributes[column][:accessor].split(".")
|
59
|
+
self.send(association.first.to_sym).send(association.last.to_sym)
|
60
|
+
else
|
61
|
+
self.send(@@valid_attributes[column][:accessor]) || column
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
|
68
|
+
include Validations
|
69
|
+
|
70
|
+
# I'll add some validations here...
|
71
|
+
def validate_location(*args)
|
72
|
+
valid_attributes.update(args.extract_options!)
|
73
|
+
validate :validate_location, valid_attributes
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end #ActiveRecord
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
module Auspost
|
3
|
+
|
4
|
+
module Postie
|
5
|
+
|
6
|
+
class Cache
|
7
|
+
|
8
|
+
# Over ride these two class variables to change your memcache settings
|
9
|
+
@@memcache_host = "localhost"
|
10
|
+
@@memcache_port = "11211"
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
if Object.const_defined? :Rails
|
14
|
+
Rails.cache
|
15
|
+
else
|
16
|
+
@cache = MemCache.new "#{@@memcache_host}:#{@@memcache_port}", :namespace => 'auspost'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(key, object)
|
21
|
+
if @cache
|
22
|
+
@cache.add(key, object) if @cache.servers.map{|s| s.alive?}.include?(true)
|
23
|
+
else
|
24
|
+
Rails.cache.write(key, object)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(key)
|
29
|
+
if @cache
|
30
|
+
@cache.get(key) if @cache.servers.map{|s| s.alive?}.include?(true)
|
31
|
+
else
|
32
|
+
Rails.cache.read(key)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Suburb
|
38
|
+
def initialize(attrs)
|
39
|
+
@suburb = attrs[:suburb].downcase
|
40
|
+
@postcode = attrs[:postcode].to_i
|
41
|
+
@state = attrs[:state].downcase
|
42
|
+
end
|
43
|
+
|
44
|
+
def suburb?(attrs)
|
45
|
+
attrs[:suburb].downcase == @suburb
|
46
|
+
end
|
47
|
+
|
48
|
+
def state?(attrs)
|
49
|
+
attrs[:state].downcase == @state.downcase
|
50
|
+
end
|
51
|
+
|
52
|
+
def postcode?(attrs)
|
53
|
+
attrs[:postcode].to_i == @postcode
|
54
|
+
end
|
55
|
+
|
56
|
+
def eql?(attrs)
|
57
|
+
suburb?(attrs) && postcode?(attrs) && state?(attrs)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_i(attrs)
|
61
|
+
(suburb?(attrs) ? 1 : 0) +
|
62
|
+
(state?(attrs) ? 1 : 0) +
|
63
|
+
(postcode?(attrs) ? 1 : 0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Result
|
68
|
+
|
69
|
+
attr_reader :status, :errors
|
70
|
+
|
71
|
+
def initialize(status)
|
72
|
+
@status = status
|
73
|
+
@errors = [] if !@status
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
class Error
|
79
|
+
|
80
|
+
attr_reader :accessor
|
81
|
+
|
82
|
+
def initialize(accessor)
|
83
|
+
@accessor = accessor
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# This is the method that returns whether a location is correct or not.
|
90
|
+
# It requires three attributes
|
91
|
+
# :postcode, :suburb & :state such as
|
92
|
+
# location?(:postcode => 2038, :suburb => "Annandale", :state => "NSW") #=> Result#status => true
|
93
|
+
# location?(:postcode => 2010, :suburb => "Annandale", :state => "NSW") #=> Result#status => false
|
94
|
+
# Result#errors => Error#accessor => :suburb && Error#message => "Annandale does not match 2010 postcode"
|
95
|
+
# The results of a request to a given postcode is cached in memcached if available or in
|
96
|
+
# your Rails.cache store if you're in a Rails project.
|
97
|
+
def location?(attrs = {})
|
98
|
+
map_attributes!(attrs)
|
99
|
+
url = "http://www1.auspost.com.au/postcodes/index.asp?Locality=&sub=1&State=&Postcode=#{CGI::escape(@postcode)}&submit1=Search"
|
100
|
+
@content = get(url)
|
101
|
+
check_results(attrs)
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def cache
|
108
|
+
@cache ||= set_cache
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_cache
|
112
|
+
@cache = Cache.new
|
113
|
+
end
|
114
|
+
|
115
|
+
def get(url)
|
116
|
+
@results = check_cache || get_and_cache(url)
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_cache
|
120
|
+
cache.read(@postcode)
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_and_cache(url)
|
124
|
+
object = open(url)
|
125
|
+
@table = Nokogiri::HTML(object).xpath('//table[3]/tr/td[2]/table/tr/td/font/table/tr[2]/td/table/tr')
|
126
|
+
content = map_results
|
127
|
+
content.delete_if{|x| x.nil?}
|
128
|
+
cache.write(@postcode, content)
|
129
|
+
content
|
130
|
+
end
|
131
|
+
|
132
|
+
def map_attributes!(attrs)
|
133
|
+
raise ArgumentError, "You must supply a suburb" if attrs[:suburb].nil?
|
134
|
+
raise ArgumentError, "You must supply a state" if attrs[:state].nil?
|
135
|
+
raise ArgumentError, "You must supply a postcode" if attrs[:postcode].nil?
|
136
|
+
attrs.values.map!{|value| value.to_s.downcase! }
|
137
|
+
@postcode = attrs[:postcode].to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
def map_results
|
141
|
+
@table.map do |row|
|
142
|
+
content = row.content.strip if row.content.include?("LOCATION:")
|
143
|
+
if content
|
144
|
+
s = content.index("LOCATION: ")
|
145
|
+
e = content.index(",")
|
146
|
+
suburb = content[s..e-1].split(" ").last
|
147
|
+
state = content[e+2..e+4]
|
148
|
+
final = Suburb.new(:suburb => suburb, :state => state, :postcode => @postcode)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def check_results(attrs)
|
154
|
+
if @results.map{|suburb| suburb.eql?(attrs)}.include?(true)
|
155
|
+
Result.new(true)
|
156
|
+
else
|
157
|
+
generate_result_with_errors(attrs)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def generate_result_with_errors(attrs)
|
162
|
+
result = Result.new(false)
|
163
|
+
error_count = @results.map{|r| r.to_i(attrs) }
|
164
|
+
main_error = @results[error_count.index(error_count.sort.last)]
|
165
|
+
result.errors << Error.new(:suburb) if !main_error.suburb?(attrs)
|
166
|
+
result.errors << Error.new(:state) if !main_error.state?(attrs)
|
167
|
+
result
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "../../spec_helper")
|
2
|
+
require File.join(File.dirname(__FILE__), "../../fixtures/postal_worker")
|
3
|
+
|
4
|
+
describe PostalWorker, "without auspost/active_record required" do
|
5
|
+
|
6
|
+
it "should not respond to a method validate_location" do
|
7
|
+
PostalWorker.should_not respond_to(:validate_location)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
describe PostalWorker, "with auspost/active_record required" do
|
14
|
+
|
15
|
+
# require needs to be in the before block, otherwise it's called when the file is required or the describe blocks are called.
|
16
|
+
# causing failure to the above spec
|
17
|
+
before(:all) do
|
18
|
+
require 'auspost/active_record'
|
19
|
+
PostalWorker.validate_location
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
@valid_attributes = {:address => "William St", :suburb => "Annandale", :postcode => "2038", :state => "NSW"}
|
24
|
+
@invalid_attributes = {:address => "William St", :suburb => "Annandale", :postcode => "2000", :state => "VIC"}
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a method validate_location" do
|
28
|
+
PostalWorker.should respond_to(:validate_location)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not be valid" do
|
32
|
+
@worker = PostalWorker.new(@invalid_attributes)
|
33
|
+
@worker.should_not be_valid
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should validate an address" do
|
37
|
+
@worker = PostalWorker.new({:suburb => "Rozelle", :postcode => "2039", :state => "NSW"})
|
38
|
+
@worker.should be_valid
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return a useful error message on the state" do
|
42
|
+
@worker = PostalWorker.new(@invalid_attributes)
|
43
|
+
@worker.should_not be_valid
|
44
|
+
@worker.errors.on(:state).should eql("VIC is not found in the 2000 postcode")
|
45
|
+
@worker.errors.on(:suburb).should eql("ANNANDALE is not found in the 2000 postcode")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return a useful error message on the suburb" do
|
49
|
+
@worker = PostalWorker.new(@valid_attributes.merge(:suburb => "Rozelle"))
|
50
|
+
@worker.should_not be_valid
|
51
|
+
@worker.errors.on(:suburb).should eql("ROZELLE is not found in the 2038 postcode")
|
52
|
+
@worker.errors.on(:state).should be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
describe MashedPostalWorker, "with options set on the validation" do
|
58
|
+
before(:all) do
|
59
|
+
require 'auspost/active_record'
|
60
|
+
MashedPostalWorker.validate_location :suburb => {:accessor => :city, :message => "That doesn't match up!"},
|
61
|
+
:postcode => {:accessor => :zipcode, :message => ["Postcode %s cannot be found", :zipcode]},
|
62
|
+
:state => {:accessor => "state.name", :message => ["%s is such a better state anyway *ducks*", "state.name"]}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should have a custom message on the suburb" do
|
66
|
+
@worker = MashedPostalWorker.new({:city => "Annandale", :zipcode => 3001, :state => State.new(:name => "NSW")})
|
67
|
+
@worker.should_not be_valid
|
68
|
+
@worker.errors.on(:suburb).should eql("That doesn't match up!")
|
69
|
+
@worker.errors.on(:state).should eql("NSW is such a better state anyway *ducks*")
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "../spec_helper")
|
2
|
+
include Auspost
|
3
|
+
|
4
|
+
describe Postie do
|
5
|
+
include Postie
|
6
|
+
|
7
|
+
it "should find Annandale and return true" do
|
8
|
+
location?({:postcode => "2038", :suburb => "Annandale", :state => "NSW"}).status.should eql(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "shouldn't find that Surry Hills is in the 2000 postcode" do
|
12
|
+
location?({:postcode => "2000", :suburb => "Surry Hills", :state => "QLD"}).status.should_not eql(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Testing this to ensure sub-strings don't pass as true
|
16
|
+
it "shoudn't find dale in 2038" do
|
17
|
+
location?({:postcode => "2038", :suburb => "dale", :state => "NSW"}).status.should_not eql(true)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "shouldn't substring search" do
|
21
|
+
location?(:postcode => "2038", :suburb => "Annandale", :state => "N").status.should_not eql(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise if there's not a postcode" do
|
25
|
+
lambda { location?(:suburb => "Surry Hills", :state => "FOO") }.should raise_error(ArgumentError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise if there's not a suburb" do
|
29
|
+
lambda { location?(:postcode => 2038, :state => "FOO") }.should raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise if there's not a state" do
|
33
|
+
lambda { location?(:suburb => "Surry Hills", :postcode => 2038) }.should raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should accept an Integer for the postcode" do
|
37
|
+
location?({:postcode => 2038, :suburb => "Annandale", :state => "NSW"}).status.should eql(true)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe Postie, " with cached" do
|
43
|
+
include Postie
|
44
|
+
|
45
|
+
it "should cache a result" do
|
46
|
+
@io = mock(IO)
|
47
|
+
@string = mock(String)
|
48
|
+
@array = mock(Array)
|
49
|
+
Postie::Cache.should_receive(:new).and_return(@io)
|
50
|
+
@io.should_receive(:read).with("2038").and_return(@string)
|
51
|
+
@string.should_receive(:map).at_least(:once).and_return(@array)
|
52
|
+
@array.should_receive(:include?).with(true).and_return(true)
|
53
|
+
location?({:postcode => "2038", :suburb => "Annandale", :state => "NSW"}).status.should eql(true)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link rel="stylesheet" href="/include/stylesheet/miniapps.css" type="text/css">
|
5
|
+
<link rel="stylesheet" href="/include/stylesheet/mainstyle.css" type="text/css">
|
6
|
+
<title>Australia Post - Postcode Search</title>
|
7
|
+
|
8
|
+
<META NAME='ROBOTS' CONTENT='ALL'>
|
9
|
+
<META NAME='description' CONTENT='Search for Australian Postcode locations online'>
|
10
|
+
<META NAME='keywords' CONTENT='Australia Post Postcode Search Find locations'>
|
11
|
+
|
12
|
+
</head>
|
13
|
+
<script type="text/JavaScript">
|
14
|
+
<!--
|
15
|
+
function MM_preloadImages() { //v3.0
|
16
|
+
var d=document; if(d.images){ if(!d.MM_p) d.MM_p=new Array();
|
17
|
+
var i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; i<a.length; i++)
|
18
|
+
if (a[i].indexOf("#")!=0){ d.MM_p[j]=new Image; d.MM_p[j++].src=a[i];}}
|
19
|
+
}
|
20
|
+
|
21
|
+
function MM_swapImgRestore() { //v3.0
|
22
|
+
var i,x,a=document.MM_sr; for(i=0;a&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc;
|
23
|
+
}
|
24
|
+
|
25
|
+
function MM_findObj(n, d) { //v4.01
|
26
|
+
var p,i,x; if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
|
27
|
+
d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
|
28
|
+
if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
|
29
|
+
for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);
|
30
|
+
if(!x && d.getElementById) x=d.getElementById(n); return x;
|
31
|
+
}
|
32
|
+
|
33
|
+
function MM_swapImage() { //v3.0
|
34
|
+
var i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array; for(i=0;i<(a.length-2);i+=3)
|
35
|
+
if ((x=MM_findObj(a[i]))!=null){document.MM_sr[j++]=x; if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];}
|
36
|
+
}
|
37
|
+
function cmdGo_Click() {
|
38
|
+
window.document.queryForm.submit(); }
|
39
|
+
//-->
|
40
|
+
</script>
|
41
|
+
<table width="780" border="0" cellspacing="0" cellpadding="0">
|
42
|
+
<tr class="top-header">
|
43
|
+
<td width="210"><a href="http://www.auspost.com.au"><img src="/auspost/images/display/logo.jpg" alt="Australia Post Home" width="182" height="45" border="0" /></a></td>
|
44
|
+
<td><img src="/auspost/images/display/b_arrow.gif" alt="Arrow" id="contact-arrow" width="13" height="14" /> <a class="white-title" href="http://www.auspost.com.au/BCP/0,1080,CH3988%257EMO19,00.html">Jobs @ Post</a> <img src="/auspost/images/display/b_arrow.gif" alt="Arrow" id="contact-arrow" width="13" height="14" /> <a class="white-title" href="http://www.auspost.com.au/CUP/0,,CHNONE%257EMO19,00.html">Contact Us</a> <span class="search-title">Search Site :</span> <form id="queryForm" name="queryForm" action="http://search.auspost.com.au/cse/auspost/default.asp" method="get"><input id="queryBox" name="q" value="" size="20" /> <a href="javascript:onClick=cmdGo_Click();" onmouseout="MM_swapImgRestore()" onmouseover="MM_swapImage('btngosearch','','/auspost/images/display/br_submit.gif',1)"><img name="btngosearch" id="btngosearch" src="/auspost/images/display/b_submit.gif" alt="submit" width="62" height="22" border="0" /></form></td>
|
45
|
+
</tr>
|
46
|
+
</table>
|
47
|
+
<table width="780" border="0" cellspacing="0" cellpadding="0">
|
48
|
+
<tr class="tabs-row">
|
49
|
+
<td width="19"> </td>
|
50
|
+
<td width="101"><a href="http://www.auspost.com.au"><img src="/auspost/images/display/b_Home.gif" alt="Home" name="Image2" width="82" height="29" border="0" id="Image2" onmouseover="MM_swapImage('Image2','','/auspost/images/display/br_Home.gif',1)" onmouseout="MM_swapImgRestore()" /></a></td>
|
51
|
+
<td width="139"><a href="http://www.auspost.com.au/CAP/0,,CH2002%257EMO19,00.html"><img src="/auspost/images/display/b_GenServices.gif" alt="General Services" width="122" height="29" border="0" id="Image3" onmouseover="MM_swapImage('Image3','','/auspost/images/display/br_GenServices.gif',1)" onmouseout="MM_swapImgRestore()" /></a></td>
|
52
|
+
<td width="199"><a href="http://www.auspost.com.au/CAP/0,,CH2006%257EMO19,00.html"><img src="/auspost/images/display/b_Billing.gif" alt="Billing & Financial" width="180" height="29" border="0" id="Image4" onmouseover="MM_swapImage('Image4','','/auspost/images/display/br_Billing.gif',1)" onmouseout="MM_swapImgRestore()" /></a></td>
|
53
|
+
<td width="154"><a href="http://www.auspost.com.au/CAP/0,,CH2005%257EMO19,00.html"><img src="/auspost/images/display/b_BusSolutions.gif" alt="Business Solutions" width="138" height="29" border="0" id="Image5" onmouseover="MM_swapImage('Image5','','/auspost/images/display/br_BusSolutions.gif',1)" onmouseout="MM_swapImgRestore()" /></a></td>
|
54
|
+
<td><a href="http://www.auspost.com.au/CAP/0,,CH2007%257EMO19,00.html"><img src="/auspost/images/display/b_AboutUs.gif" alt="About Us" width="85" height="29" border="0" id="Image6" onmouseover="MM_swapImage('Image6','','/auspost/images/display/br_AboutUs.gif',1)" onmouseout="MM_swapImgRestore()" /></a></td>
|
55
|
+
<td width="50"><div align="right"><img src="/auspost/images/display/cnr_btm_r.gif" alt="corner" width="39" height="29" /></div></td>
|
56
|
+
</tr>
|
57
|
+
<tr>
|
58
|
+
<td colspan="7"><img src="/auspost/images/display/transparent 3x3.gif" alt="Blank" width="8" height="8" /></td>
|
59
|
+
</tr>
|
60
|
+
</table>
|
61
|
+
<table border="0" cellpadding="0" cellspacing="0" width="780">
|
62
|
+
<tr>
|
63
|
+
<td valign="top" id="td-site-app">
|
64
|
+
|
65
|
+
<ul id="site-apps" style="padding-right: 5px;">
|
66
|
+
<li><img src="/auspost/images/display/h_Useful_Tools.gif" alt="Useful Tools" width="113" height="29" /></li>
|
67
|
+
<li><img src="/auspost/images/display/i_FindPostcode.gif" alt="Find a postcode" width="22" height="22" /> <a class="min-apps" href="http://www1.auspost.com.au/postcodes/">Find a postcode</a></li>
|
68
|
+
<li><img src="/auspost/images/display/i_CalcPostageRates.gif" alt="Calculate postage rates" width="22" height="22" /> <a class="min-apps" href="http://www1.auspost.com.au/pac/">Calculate postage rates</a></li>
|
69
|
+
<li><img src="/auspost/images/display/i_ExpressPostPlatTrackg.gif" alt="Australia post delivery tracking" width="22" height="22" /> <a class="min-apps" href="http://www.auspost.com.au/track">Track my item</a></li>
|
70
|
+
<li><img src="/auspost/images/display/i_FindPostalOutlet.gif" alt="Find a postal outlet" width="22" height="22" /> <a class="min-apps" href="http://www.auspost.com.au/pol/">Find a postal outlet</a></li>
|
71
|
+
<li><img src="/auspost/images/display/i_FindPostalBox.gif" alt="Find a street posting box" width="22" height="22" /> <a class="min-apps" href="http://www.auspost.com.au/pol/">Find a street posting box</a></li>
|
72
|
+
<li><img src="/auspost/images/display/i_PayBillsOnline.gif" alt="Pay bills online" width="22" height="22" /> <a class="min-apps" href="http://www.postbillpay.com.au/">Pay bills online</a></li>
|
73
|
+
<li><img src="/auspost/images/display/i_IntnlPostageDetails.gif" alt="International Postage Details" width="22" height="22" /> <a class="min-apps" href="http://www1.auspost.com.au/international/">International postage details</a></li>
|
74
|
+
<li><img src="/auspost/images/display/i_PostGuides.gif" alt="Post guides" width="22" height="22" /> <a class="min-apps" href="http://www.auspost.com.au/BCP/0,,CH3851%257EMO19,00.html">Post guides</a></li>
|
75
|
+
</ul>
|
76
|
+
|
77
|
+
</td>
|
78
|
+
<td valign="top">
|
79
|
+
<table width="600" border="0" cellpadding="0" cellspacing="10">
|
80
|
+
<tr valign="top">
|
81
|
+
<td width="493" align="center" valign="top"><font face="verdana,Helv,Arial" size="2">
|
82
|
+
|
83
|
+
|
84
|
+
<table border="0" cellpadding="0" cellspacing="0" width="490">
|
85
|
+
<tr>
|
86
|
+
<!-- banner header -->
|
87
|
+
<td colspan="2" bgcolor="#B9E3DE"><img src="../images_new/postcode_search/funct_postcodesearch.gif" alt="Postcode Search" width="327" height="32"></td>
|
88
|
+
</tr>
|
89
|
+
<tr>
|
90
|
+
<!-- Results row -->
|
91
|
+
<td width="330" valign="top"><table width="330" border="0" cellpadding="5" cellspacing="0">
|
92
|
+
|
93
|
+
<tr>
|
94
|
+
<td valign="top" width="7" bgcolor="#ff0000"><font face="verdana, arial, helv" size="2" color="#ffffff"><strong>1.</strong></td>
|
95
|
+
<td><font face="arial, helv" size="2"> <strong>LOCATION:</strong> ANNANDALE, NSW<br />
|
96
|
+
|
97
|
+
<strong>POSTCODE:</strong> 2038<br />
|
98
|
+
|
99
|
+
<strong>CATEGORY:</strong> Delivery Area<br />
|
100
|
+
|
101
|
+
</td>
|
102
|
+
</tr>
|
103
|
+
<tr>
|
104
|
+
<td colspan="2"><hr noshade size="1">
|
105
|
+
</td>
|
106
|
+
</tr>
|
107
|
+
|
108
|
+
</table></td>
|
109
|
+
<td width="150" valign="top" bgcolor="#B9E3DE"><table width="150" border="0" cellpadding="5" cellspacing="0">
|
110
|
+
<!-- Results summary table -->
|
111
|
+
<tr>
|
112
|
+
<!-- Show results for each state -->
|
113
|
+
<td width="150" style="background-color: #B9E3DE; font-size:100%"><a href="index.asp">Search Again</a>
|
114
|
+
<hr noshade size="1">
|
115
|
+
<strong>Returned Matches:</strong> 1
|
116
|
+
<hr noshade size="1">
|
117
|
+
|
118
|
+
<strong>Breakdown</strong>
|
119
|
+
<br />New South Wales: 1
|
120
|
+
</td>
|
121
|
+
</tr>
|
122
|
+
|
123
|
+
</table></td>
|
124
|
+
</tr>
|
125
|
+
</table>
|
126
|
+
|
127
|
+
</td>
|
128
|
+
</tr>
|
129
|
+
</table>
|
130
|
+
</td>
|
131
|
+
</tr>
|
132
|
+
</table>
|
133
|
+
|
134
|
+
<table width="780" border="0" cellspacing="0" cellpadding="0">
|
135
|
+
<!---------- SPACER ------------------------------------------------------------------->
|
136
|
+
<tr>
|
137
|
+
<td valign="top" height="20">
|
138
|
+
</td>
|
139
|
+
</tr>
|
140
|
+
<!---------- end SPACER --------------------------------------------------------------->
|
141
|
+
|
142
|
+
<!---------- FIRST LEVEL NAVIGATION ------------------------------------------------------>
|
143
|
+
<tr>
|
144
|
+
<td id="td-foot">
|
145
|
+
<br />
|
146
|
+
<ul id="footer-list">
|
147
|
+
<li><img src="/auspost/images/display/a_right.gif" alt="Arrow" width="4" height="7" /> <a class="footer" href="http://www.auspost.com.au/APC/CDA/Sitemap/APC_CDA_Sitemap/0,1084,CHNONE%257EMO19,00.html">Site map</a> </li>
|
148
|
+
<li><img src="/auspost/images/display/a_right.gif" alt="Arrow" width="4" height="7" /> <a class="footer" href="http://www.auspost.com.au/CUP/0,1050,CHNONE%257EMO19,00.html">Contact Us</a> </li>
|
149
|
+
<li><img src="/auspost/images/display/a_right.gif" alt="Arrow" width="4" height="7" /> <a class="footer" href="http://www.auspost.com.au/APC/CDA/Site_Governance/APC_CDA_Privacy_Policy/">Privacy</a> </li>
|
150
|
+
<li><img src="/auspost/images/display/a_right.gif" alt="Arrow" width="4" height="7" /> <a class="footer" href="http://www.auspost.com.au/APC/CDA/Site_Governance/APC_CDA_Terms_Conditions/">Terms & Conditions </a></li>
|
151
|
+
</ul>
|
152
|
+
|
153
|
+
|
154
|
+
<!-- Track File Downloads with Google -->
|
155
|
+
<script language="javascript" type="text/javascript" src="/APC/CDA/CSS/gatag.js" ></script>
|
156
|
+
|
157
|
+
<!--Google tracking -->
|
158
|
+
<script type="text/javascript">
|
159
|
+
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
160
|
+
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
161
|
+
</script>
|
162
|
+
|
163
|
+
<script type="text/javascript">
|
164
|
+
<!-- Auspost roll up account -->
|
165
|
+
try {
|
166
|
+
var APTracker = _gat._getTracker("UA-2225711-5");
|
167
|
+
APTracker._setDomainName(".auspost.com.au");
|
168
|
+
APTracker._trackPageview("WWW1" + document.location.pathname);
|
169
|
+
} catch(err) {}
|
170
|
+
|
171
|
+
<!-- Auspost account -->
|
172
|
+
try {
|
173
|
+
var pageTracker = _gat._getTracker("UA-2225711-1");
|
174
|
+
pageTracker._setDomainName(".auspost.com.au");
|
175
|
+
pageTracker._trackPageview();
|
176
|
+
} catch(err) {}
|
177
|
+
|
178
|
+
<!-- WWW1 account -->
|
179
|
+
try {
|
180
|
+
var pageTracker = _gat._getTracker("UA-2225711-3");
|
181
|
+
pageTracker._setDomainName(".auspost.com.au");
|
182
|
+
pageTracker._trackPageview();
|
183
|
+
} catch(err) {}
|
184
|
+
</script>
|
185
|
+
|
186
|
+
</body>
|
187
|
+
</html>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
gem 'sqlite3-ruby'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(
|
6
|
+
:adapter => "sqlite3",
|
7
|
+
:database => ":memory:"
|
8
|
+
)
|
9
|
+
ActiveRecord::Base.connection.create_table(:postal_workers) do |t|
|
10
|
+
t.string :address
|
11
|
+
t.string :postcode
|
12
|
+
t.string :suburb
|
13
|
+
t.string :state
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
ActiveRecord::Base.connection.create_table(:mashed_postal_workers) do |t|
|
18
|
+
t.string :addr
|
19
|
+
t.string :zipcode
|
20
|
+
t.string :city
|
21
|
+
t.integer :state_id
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveRecord::Base.connection.create_table(:states) do |t|
|
25
|
+
t.string :name
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class PostalWorker < ActiveRecord::Base;end
|
30
|
+
|
31
|
+
class MashedPostalWorker < ActiveRecord::Base
|
32
|
+
|
33
|
+
belongs_to :state
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
class State < ActiveRecord::Base
|
38
|
+
has_many :mashed_postal_workers
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib auspost]))
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
# == Mock Framework
|
7
|
+
#
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
10
|
+
#
|
11
|
+
# config.mock_with :mocha
|
12
|
+
# config.mock_with :flexmock
|
13
|
+
# config.mock_with :rr
|
14
|
+
end
|
15
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: auspost
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cameron Barrie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-02 00:00:00 +11:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bones
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.2.0
|
24
|
+
version:
|
25
|
+
description: |-
|
26
|
+
Give you the ability to search the Australia Post Web site with either Suburb or Postcode, and return an array of Locations.
|
27
|
+
Also add's validation methods to ActiveRecord Objects to allow you to validate that A Postcode + State + Suburb is correct
|
28
|
+
email: camwritescode@gmail.com
|
29
|
+
executables:
|
30
|
+
- auspost
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files:
|
34
|
+
- History.txt
|
35
|
+
- README.txt
|
36
|
+
- bin/auspost
|
37
|
+
files:
|
38
|
+
- .bnsignore
|
39
|
+
- History.txt
|
40
|
+
- README.txt
|
41
|
+
- Rakefile
|
42
|
+
- bin/auspost
|
43
|
+
- lib/auspost.rb
|
44
|
+
- lib/auspost/active_record.rb
|
45
|
+
- lib/auspost/active_record/validations.rb
|
46
|
+
- lib/auspost/postie.rb
|
47
|
+
- spec/auspost/active_record/validations_spec.rb
|
48
|
+
- spec/auspost/postie_spec.rb
|
49
|
+
- spec/fixtures/auspost.html
|
50
|
+
- spec/fixtures/postal_worker.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://www.camwritescode.com/auspost
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --main
|
59
|
+
- README.txt
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project: auspost
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Give you the ability to search the Australia Post Web site with either Suburb or Postcode, and return an array of Locations
|
81
|
+
test_files:
|
82
|
+
- spec/auspost/active_record/validations_spec.rb
|
83
|
+
- spec/auspost/postie_spec.rb
|