trackerific 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +62 -44
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/changelog +9 -0
- data/lib/trackerific/configuration.rb +2 -2
- data/lib/trackerific/details.rb +36 -10
- data/lib/trackerific/event.rb +9 -7
- data/lib/trackerific/service.rb +22 -6
- data/lib/trackerific/services/fedex.rb +11 -7
- data/lib/trackerific/services/mock_service.rb +23 -14
- data/lib/trackerific/services/ups.rb +12 -8
- data/lib/trackerific/services/usps.rb +114 -21
- data/spec/fixtures/usps_city_state_lookup_response.xml +8 -0
- data/spec/lib/helpers/options_helper_spec.rb +3 -3
- data/spec/lib/trackerific/configuration_spec.rb +35 -0
- data/spec/lib/trackerific/details_spec.rb +71 -11
- data/spec/lib/trackerific/event_spec.rb +34 -19
- data/spec/lib/trackerific/service_spec.rb +3 -3
- data/spec/lib/trackerific/services/fedex_spec.rb +16 -3
- data/spec/lib/trackerific/services/mock_service_spec.rb +19 -4
- data/spec/lib/trackerific/services/ups_spec.rb +17 -2
- data/spec/lib/trackerific/services/usps_spec.rb +60 -11
- data/spec/lib/trackerific_spec.rb +7 -5
- data/trackerific.gemspec +5 -27
- metadata +8 -30
- data/doc/OptionsHelper.html +0 -287
- data/doc/Trackerific/Configuration.html +0 -354
- data/doc/Trackerific/Details.html +0 -565
- data/doc/Trackerific/Error.html +0 -127
- data/doc/Trackerific/Event.html +0 -639
- data/doc/Trackerific/FedEx.html +0 -558
- data/doc/Trackerific/Service.html +0 -579
- data/doc/Trackerific/UPS.html +0 -532
- data/doc/Trackerific/USPS.html +0 -568
- data/doc/Trackerific.html +0 -833
- data/doc/_index.html +0 -226
- data/doc/class_list.html +0 -47
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -53
- data/doc/css/style.css +0 -320
- data/doc/file.README.html +0 -288
- data/doc/file_list.html +0 -49
- data/doc/frames.html +0 -13
- data/doc/index.html +0 -288
- data/doc/js/app.js +0 -205
- data/doc/js/full_list.js +0 -150
- data/doc/js/jquery.js +0 -16
- data/doc/method_list.html +0 -294
- data/doc/top-level-namespace.html +0 -103
data/README.rdoc
CHANGED
@@ -18,10 +18,20 @@ configure your credentials for each service.
|
|
18
18
|
# config/initializers/trackerific.rb
|
19
19
|
require 'trackerific'
|
20
20
|
Trackerific.configure do |config|
|
21
|
-
config.fedex
|
22
|
-
|
23
|
-
|
21
|
+
config.fedex :account => 'account',
|
22
|
+
:meter => '123456789'
|
23
|
+
|
24
|
+
config.ups :key => 'key',
|
25
|
+
:user_id => 'userid',
|
26
|
+
:password => 'secret'
|
27
|
+
|
28
|
+
config.usps :user_id => 'userid',
|
29
|
+
:use_city_state_lookup => true
|
24
30
|
end
|
31
|
+
|
32
|
+
For USPS packages, the option :use_city_state_lookup defaults to false, and will
|
33
|
+
only work if you have access to USPS's CityStateLookup API. If you can enable
|
34
|
+
it, this feature will provide the location for USPS package events.
|
25
35
|
|
26
36
|
=== Tracking with Automatic Service Discovery
|
27
37
|
|
@@ -85,6 +95,14 @@ Note that events.last will return the first event the tracking provider
|
|
85
95
|
supplied. This is because the events are listed in LIFO order, so the most
|
86
96
|
recent events will always be at the top of the list.
|
87
97
|
|
98
|
+
=== City / State Lookup Via USPS
|
99
|
+
|
100
|
+
If you have access to the USPS CityStateLookup API, you can use Trackerific to
|
101
|
+
look up the city and state of a zipcode.
|
102
|
+
|
103
|
+
usps = Trackerific::USPS.new :user_id => 'userid'
|
104
|
+
usps.city_state_lookup "90210" # => { :city => 'BEVERLY HILLS', :state => 'CA', :zip => '90210' }
|
105
|
+
|
88
106
|
=== Exception handling
|
89
107
|
|
90
108
|
Exception handling is esssential for tracking packages. If, for example,
|
@@ -98,60 +116,60 @@ example on how to handle Trackerific::Errors:
|
|
98
116
|
puts e.message
|
99
117
|
end
|
100
118
|
|
101
|
-
|
119
|
+
or for a Rails application:
|
120
|
+
|
121
|
+
# in app/controllers/application_controller.rb
|
122
|
+
rescue_from Trackerific::Error do |exception|
|
123
|
+
redirect_to root_url, :alert => exception.message
|
124
|
+
end
|
125
|
+
|
126
|
+
== Writing a Custom Service
|
102
127
|
|
103
|
-
Here is a
|
128
|
+
Here is a spec for writing a custom trackerific service:
|
104
129
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
130
|
+
describe Trackerific::CustomService do
|
131
|
+
specify("it should descend from Trackerific::Service") {
|
132
|
+
Trackerific::CustomService.superclass.should be Trackerific::Service
|
133
|
+
}
|
134
|
+
describe :track_package do
|
135
|
+
before do
|
136
|
+
@valid_package_id = 'valid package id'
|
137
|
+
@invalid_package_id = 'invalid package id'
|
138
|
+
@service = Trackerific::CustomService.new :required => 'option'
|
111
139
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
140
|
+
context "with a successful response from the server" do
|
141
|
+
before(:each) do
|
142
|
+
@tracking = @service.track_package(@valid_package_id)
|
143
|
+
end
|
144
|
+
subject { @tracking }
|
145
|
+
it("should return a Trackerific::Details") { should be_a Trackerific::Details }
|
146
|
+
describe :summary do
|
147
|
+
subject { @tracking.summary }
|
148
|
+
it { should_not be_empty }
|
149
|
+
end
|
115
150
|
end
|
116
|
-
|
117
|
-
|
118
|
-
Trackerific::Details.new(
|
119
|
-
"summary",
|
120
|
-
[
|
121
|
-
Trackerific::Event.new(Time.now, "description", "location"),
|
122
|
-
Trackerific::Event.new(Time.now, "description", "location")
|
123
|
-
]
|
124
|
-
)
|
151
|
+
context "with an error response from the server" do
|
152
|
+
specify { lambda { @service.track_package(@invalid_package_id) }.should raise_error(Trackerific::Error) }
|
125
153
|
end
|
126
154
|
end
|
127
|
-
end
|
128
|
-
|
129
|
-
spec/lib/trackerific/services/my_tracking_service_spec.rb:
|
130
|
-
describe "Trackerific::MyTrackingService" do
|
131
155
|
describe :required_options do
|
132
|
-
subject { Trackerific::
|
133
|
-
it { should include(:
|
134
|
-
it { should include(:options) }
|
156
|
+
subject { Trackerific::CustomService.required_options }
|
157
|
+
it { should include(:required) }
|
135
158
|
end
|
136
|
-
describe :
|
137
|
-
it "should
|
138
|
-
Trackerific::
|
159
|
+
describe :valid_options do
|
160
|
+
it "should include required_options" do
|
161
|
+
valid = Trackerific::CustomService.valid_options
|
162
|
+
Trackerific::CustomService.required_options.each do |opt|
|
163
|
+
valid.should include opt
|
164
|
+
end
|
139
165
|
end
|
140
166
|
end
|
141
|
-
describe :
|
142
|
-
|
167
|
+
describe :package_id_matchers do
|
168
|
+
subject { Trackerific::CustomService.package_id_matchers }
|
169
|
+
it("should be an Array of Regexp") { should each { |m| m.should be_a Regexp } }
|
143
170
|
end
|
144
171
|
end
|
145
172
|
|
146
|
-
Please make sure to include comments, documentation, and specs for your service.
|
147
|
-
Trackerific uses {RSpec}[https://github.com/dchelimsky/rspec] for tests,
|
148
|
-
{simplecov}[https://github.com/colszowka/simplecov] for code coverage,
|
149
|
-
and {Yardoc}[http://yardoc.org/] for documentation. You can also take advantage
|
150
|
-
of {yardstick}[https://github.com/dkubb/yardstick] to help verify the coverage
|
151
|
-
of the comments of your code. You can use the rake task:
|
152
|
-
rake yardstick_measure
|
153
|
-
which will generate a measurement/report.txt file.
|
154
|
-
|
155
173
|
=== Testing with Trackerific
|
156
174
|
|
157
175
|
Trackerific provides a mocked service you can use in your unit tests of your
|
data/Rakefile
CHANGED
@@ -16,7 +16,7 @@ Jeweler::Tasks.new do |gem|
|
|
16
16
|
gem.homepage = "http://github.com/travishaynes/trackerific"
|
17
17
|
gem.license = "MIT"
|
18
18
|
gem.summary = %Q{Trackerific provides package tracking to Rails.}
|
19
|
-
gem.description = %Q{
|
19
|
+
gem.description = %Q{Package tracking made easy for Rails. Currently supported services include FedEx, UPS, and USPS.}
|
20
20
|
gem.email = "travis.j.haynes@gmail.com"
|
21
21
|
gem.authors = ["Travis Haynes"]
|
22
22
|
gem.rubyforge_project = "trackerific"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/changelog
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Mon Jun 27 2011 - Travis Haynes <travis.j.haynes@gmail.com>
|
2
|
+
|
3
|
+
* Added support for USPS City / State API for looking up a city and state by
|
4
|
+
its zipcode.
|
5
|
+
* Renamed required_options to required_parameters. (Makes much more sense.)
|
6
|
+
* Added a lot more specs for more code coverage.
|
7
|
+
* Changed the way that Details and Events are initialized. Using options
|
8
|
+
instead of parameters.
|
9
|
+
* Removed doc folder from git, since it is automatically generated with yard
|
@@ -18,8 +18,8 @@ module Trackerific
|
|
18
18
|
unless args.empty?
|
19
19
|
# Only accept Hashes
|
20
20
|
raise ArgumentError unless args[0].class == Hash
|
21
|
-
# Validate configuration values against the required
|
22
|
-
validate_options args[0], Trackerific.service_get(sym).
|
21
|
+
# Validate configuration values against the required parameters for that service
|
22
|
+
validate_options args[0], Trackerific.service_get(sym).required_parameters
|
23
23
|
# Store the configuration options
|
24
24
|
@options[sym] = args[0]
|
25
25
|
end
|
data/lib/trackerific/details.rb
CHANGED
@@ -2,18 +2,23 @@ module Trackerific
|
|
2
2
|
# Details returned when tracking a package. Stores the package identifier,
|
3
3
|
# a summary, and the events.
|
4
4
|
class Details
|
5
|
+
include OptionsHelper
|
6
|
+
|
5
7
|
# Provides a new instance of Details
|
6
|
-
# @param [
|
7
|
-
# @param [String] summary a summary of the tracking status
|
8
|
-
# @param [Array, Trackerific::Event] events the tracking events
|
8
|
+
# @param [Hash] details The details for this package
|
9
9
|
# @api private
|
10
|
-
def initialize(
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def initialize(details = {})
|
11
|
+
required = [:package_id, :summary, :events]
|
12
|
+
valid = required + [:weight, :via]
|
13
|
+
validate_options(details, required, valid)
|
14
|
+
@package_id = details[:package_id]
|
15
|
+
@summary = details[:summary]
|
16
|
+
@events = details[:events]
|
17
|
+
@weight = details[:weight] || nil
|
18
|
+
@via = details[:via] || nil
|
14
19
|
end
|
15
20
|
|
16
|
-
#
|
21
|
+
# The package identifier
|
17
22
|
# @example Get the id of a tracked package
|
18
23
|
# details.package_id # => the package identifier
|
19
24
|
# @return [String] the package identifier
|
@@ -22,7 +27,7 @@ module Trackerific
|
|
22
27
|
@package_id
|
23
28
|
end
|
24
29
|
|
25
|
-
#
|
30
|
+
# Summary of the package's tracking events
|
26
31
|
# @example Get the summary of a tracked package
|
27
32
|
# details.summary # => Summary of the tracking events (i.e. Delivered)
|
28
33
|
# @return [String] a summary of the tracking status
|
@@ -31,7 +36,7 @@ module Trackerific
|
|
31
36
|
@summary
|
32
37
|
end
|
33
38
|
|
34
|
-
#
|
39
|
+
# The events for this package
|
35
40
|
# @example Print all the events for a tracked package
|
36
41
|
# puts details.events
|
37
42
|
# @example Get the date the package was shipped
|
@@ -45,5 +50,26 @@ module Trackerific
|
|
45
50
|
def events
|
46
51
|
@events
|
47
52
|
end
|
53
|
+
|
54
|
+
# The weight of the package (may not be supported by all services)
|
55
|
+
# @example Get the weight of a package
|
56
|
+
# details.weight[:weight] # => the weight
|
57
|
+
# details.weight[:units] # => the units of measurement for the weight (i.e. "LBS")
|
58
|
+
# @return [Hash] Example: { units: 'LBS', weight: 19.1 }
|
59
|
+
# @api public
|
60
|
+
def weight
|
61
|
+
@weight
|
62
|
+
end
|
63
|
+
|
64
|
+
# Example: UPS 2ND DAY AIR. May not be supported by all services
|
65
|
+
# @example Get how the package was shipped
|
66
|
+
# ups = Trackerific::UPS.new :user_id => "userid"
|
67
|
+
# details = ups.track_package "1Z12345E0291980793"
|
68
|
+
# details.via # => "UPS 2ND DAY AIR"
|
69
|
+
# @return [String] The service used to ship the package
|
70
|
+
# @api public
|
71
|
+
def via
|
72
|
+
@via
|
73
|
+
end
|
48
74
|
end
|
49
75
|
end
|
data/lib/trackerific/event.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
module Trackerific
|
2
2
|
# Provides details for a tracking event
|
3
3
|
class Event
|
4
|
+
include OptionsHelper
|
5
|
+
|
4
6
|
# Provides a new instance of Event
|
5
|
-
# @param [
|
6
|
-
# @param [String] description the event's description
|
7
|
-
# @param [String] location where the event took place
|
7
|
+
# @param [Hash] details The details of the event
|
8
8
|
# @api private
|
9
|
-
def initialize(
|
10
|
-
|
11
|
-
|
12
|
-
@
|
9
|
+
def initialize(details = {})
|
10
|
+
required_details = [:date, :description, :location]
|
11
|
+
validate_options details, required_details
|
12
|
+
@date = details[:date]
|
13
|
+
@description = details[:description]
|
14
|
+
@location = details[:location]
|
13
15
|
end
|
14
16
|
|
15
17
|
# The date and time of the event
|
data/lib/trackerific/service.rb
CHANGED
@@ -3,10 +3,10 @@ module Trackerific
|
|
3
3
|
class Service
|
4
4
|
include OptionsHelper
|
5
5
|
|
6
|
-
# Creates a new instance of Trackerific::Service
|
6
|
+
# Creates a new instance of Trackerific::Service
|
7
7
|
# @api private
|
8
8
|
def initialize(options = {})
|
9
|
-
validate_options options, self.class.
|
9
|
+
validate_options options, self.class.required_parameters, self.class.valid_options
|
10
10
|
@options = options
|
11
11
|
end
|
12
12
|
|
@@ -48,21 +48,37 @@ module Trackerific
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# An array of options that are required to create a new instance of this class
|
51
|
-
# @return [Array] the required
|
51
|
+
# @return [Array] the required parameters
|
52
52
|
# @example Override this method in your custom tracking service to enforce some options
|
53
53
|
# module Trackerific
|
54
54
|
# class MyTrackingService < Service
|
55
|
-
# def self.
|
55
|
+
# def self.required_parameters
|
56
56
|
# [:all, :these, :are, :required]
|
57
57
|
# end
|
58
58
|
# end
|
59
59
|
# end
|
60
60
|
# @api semipublic
|
61
|
-
def
|
61
|
+
def required_parameters
|
62
62
|
[]
|
63
63
|
end
|
64
64
|
|
65
|
-
#
|
65
|
+
# An array of valid options used for creating this class
|
66
|
+
# @return [Array] the valid options
|
67
|
+
# @example Override this method in your custom tracking service to add options
|
68
|
+
# module Trackerific
|
69
|
+
# class MyTrackingService < Service
|
70
|
+
# def self.valid_options
|
71
|
+
# # NOTE: make sure to include the required parameters in this list!
|
72
|
+
# required_parameters + [:some, :more, :options]
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
# @api semipublic
|
77
|
+
def valid_options
|
78
|
+
required_parameters + []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Provides a humanized string that provides the name of the service
|
66
82
|
# @return [String] the service name
|
67
83
|
# @note This defaults to using the class name.
|
68
84
|
# @example Override this method in your custom tracking service to provide a name
|
@@ -17,11 +17,11 @@ module Trackerific
|
|
17
17
|
[ /^[0-9]{15}$/ ]
|
18
18
|
end
|
19
19
|
|
20
|
-
# Returns an Array of required
|
21
|
-
# @return [Array] required
|
20
|
+
# Returns an Array of required parameters used when creating a new instance
|
21
|
+
# @return [Array] required parameters for tracking a FedEx package are :account
|
22
22
|
# and :meter
|
23
23
|
# @api private
|
24
|
-
def
|
24
|
+
def required_parameters
|
25
25
|
[:account, :meter]
|
26
26
|
end
|
27
27
|
end
|
@@ -52,13 +52,17 @@ module Trackerific
|
|
52
52
|
date = Time.parse("#{e["Date"]} #{e["Time"]}")
|
53
53
|
desc = e["Description"]
|
54
54
|
addr = e["Address"]
|
55
|
-
events << Trackerific::Event.new(
|
55
|
+
events << Trackerific::Event.new(
|
56
|
+
:date => date,
|
57
|
+
:description => desc,
|
58
|
+
:location => "#{addr["StateOrProvinceCode"]} #{addr["PostalCode"]}"
|
59
|
+
)
|
56
60
|
end
|
57
61
|
# Return a Trackerific::Details containing all the events
|
58
62
|
Trackerific::Details.new(
|
59
|
-
details["TrackingNumber"],
|
60
|
-
details["StatusDescription"],
|
61
|
-
events
|
63
|
+
:package_id => details["TrackingNumber"],
|
64
|
+
:summary => details["StatusDescription"],
|
65
|
+
:events => events
|
62
66
|
)
|
63
67
|
end
|
64
68
|
|
@@ -10,17 +10,14 @@ module Trackerific
|
|
10
10
|
# @return [Array, Regexp] the regular expression
|
11
11
|
# @api private
|
12
12
|
def package_id_matchers
|
13
|
-
|
14
|
-
|
15
|
-
else
|
16
|
-
return [ ] # no matchers in production mode
|
17
|
-
end
|
13
|
+
return [ /XXXXXXXXXX/, /XXXxxxxxxx/ ] unless Rails.env.production?
|
14
|
+
return [ ]
|
18
15
|
end
|
19
16
|
|
20
|
-
# Returns an Array of required
|
21
|
-
# @return [Array] required
|
17
|
+
# Returns an Array of required parameters used when creating a new instance
|
18
|
+
# @return [Array] required parameters
|
22
19
|
# @api private
|
23
|
-
def
|
20
|
+
def required_parameters
|
24
21
|
[ ]
|
25
22
|
end
|
26
23
|
end
|
@@ -38,12 +35,24 @@ module Trackerific
|
|
38
35
|
super
|
39
36
|
if package_id == "XXXXXXXXXX"
|
40
37
|
Trackerific::Details.new(
|
41
|
-
package_id,
|
42
|
-
"Your package was delivered.",
|
43
|
-
[
|
44
|
-
Trackerific::Event.new(
|
45
|
-
|
46
|
-
|
38
|
+
:package_id => package_id,
|
39
|
+
:summary => "Your package was delivered.",
|
40
|
+
:events => [
|
41
|
+
Trackerific::Event.new(
|
42
|
+
:date => Date.today,
|
43
|
+
:description => "Package delivered.",
|
44
|
+
:location => "SANTA MARIA, CA"
|
45
|
+
),
|
46
|
+
Trackerific::Event.new(
|
47
|
+
:date => Date.today - 1,
|
48
|
+
:description => "Package scanned.",
|
49
|
+
:location => "SANTA BARBARA, CA"
|
50
|
+
),
|
51
|
+
Trackerific::Event.new(
|
52
|
+
:date => Date.today - 2,
|
53
|
+
:description => "Package picked up for delivery.",
|
54
|
+
:location => "LOS ANGELES, CA"
|
55
|
+
)
|
47
56
|
]
|
48
57
|
)
|
49
58
|
else
|
@@ -21,10 +21,10 @@ module Trackerific
|
|
21
21
|
def package_id_matchers
|
22
22
|
[ /^.Z/, /^[HK].{10}$/ ]
|
23
23
|
end
|
24
|
-
# The required
|
25
|
-
# @return [Array] the required
|
24
|
+
# The required parameters for tracking a UPS package
|
25
|
+
# @return [Array] the required parameters for tracking a UPS package
|
26
26
|
# @api private
|
27
|
-
def
|
27
|
+
def required_parameters
|
28
28
|
[:key, :user_id, :password]
|
29
29
|
end
|
30
30
|
end
|
@@ -64,7 +64,7 @@ module Trackerific
|
|
64
64
|
activity = [activity] if activity.is_a? Hash
|
65
65
|
# UPS does not provide a summary, so we'll just use the last tracking status
|
66
66
|
summary = activity.first['Status']['StatusType']['Description'].titleize
|
67
|
-
|
67
|
+
events = []
|
68
68
|
activity.each do |a|
|
69
69
|
# the time format from UPS is HHMMSS, which cannot be directly converted
|
70
70
|
# to a Ruby time.
|
@@ -75,13 +75,17 @@ module Trackerific
|
|
75
75
|
date = DateTime.parse("#{date} #{hours}:#{minutes}:#{seconds}")
|
76
76
|
desc = a['Status']['StatusType']['Description'].titleize
|
77
77
|
loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
|
78
|
-
|
78
|
+
events << Trackerific::Event.new(
|
79
|
+
:date => date,
|
80
|
+
:description => desc,
|
81
|
+
:location => loc
|
82
|
+
)
|
79
83
|
end
|
80
84
|
|
81
85
|
Trackerific::Details.new(
|
82
|
-
@package_id,
|
83
|
-
summary,
|
84
|
-
|
86
|
+
:package_id => @package_id,
|
87
|
+
:summary => summary,
|
88
|
+
:events => events
|
85
89
|
)
|
86
90
|
end
|
87
91
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'active_support/core_ext/object/to_query'
|
2
3
|
|
3
4
|
module Trackerific
|
4
5
|
require 'builder'
|
@@ -19,12 +20,19 @@ module Trackerific
|
|
19
20
|
[ /^E\D{1}\d{9}\D{2}$|^9\d{15,21}$/ ]
|
20
21
|
end
|
21
22
|
|
22
|
-
# The required
|
23
|
-
# @return [Array] the required
|
23
|
+
# The required parameters for tracking a UPS package
|
24
|
+
# @return [Array] the required parameters for tracking a UPS package
|
24
25
|
# @api private
|
25
|
-
def
|
26
|
+
def required_parameters
|
26
27
|
[:user_id]
|
27
28
|
end
|
29
|
+
|
30
|
+
# List of all valid options for tracking a UPS package
|
31
|
+
# @return [Array] the valid options for tracking a UPS package
|
32
|
+
# @api private
|
33
|
+
def valid_options
|
34
|
+
required_parameters + [:use_city_state_lookup]
|
35
|
+
end
|
28
36
|
end
|
29
37
|
|
30
38
|
# Tracks a USPS package
|
@@ -42,44 +50,129 @@ module Trackerific
|
|
42
50
|
Rails.env.production? ? "/ShippingAPI.dll" : "/ShippingAPITest.dll",
|
43
51
|
:query => {
|
44
52
|
:API => 'TrackV2',
|
45
|
-
:XML =>
|
53
|
+
:XML => build_tracking_xml_request
|
46
54
|
}.to_query
|
47
55
|
)
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
# tracking response is malformed
|
52
|
-
raise Trackerific::Error, response['Error']['Description'] unless response['Error'].nil?
|
53
|
-
raise Trackerific::Error, "Tracking information not found in response from server." if response['TrackResponse'].nil?
|
56
|
+
# raise any errors
|
57
|
+
error = check_response_for_errors(response, :TrackV2)
|
58
|
+
raise error unless error.nil?
|
54
59
|
# get the tracking information from the response, and convert into a
|
55
60
|
# Trackerific::Details
|
56
61
|
tracking_info = response['TrackResponse']['TrackInfo']
|
57
|
-
|
62
|
+
events = []
|
63
|
+
# check if we should look up the exact location of the details
|
64
|
+
use_city_state_lookup = @options[:use_city_state_lookup] || false
|
65
|
+
# parse the details
|
58
66
|
tracking_info['TrackDetail'].each do |d|
|
59
67
|
# each tracking detail is a string in this format:
|
60
|
-
# MM DD HH:MM am/pm DESCRIPTION CITY STATE ZIP
|
61
|
-
# unfortunately, it will not be possible to tell the difference between
|
62
|
-
# the location, and the summary. So, for USPS, the location will be in
|
63
|
-
# the summary
|
68
|
+
# MM DD HH:MM am/pm DESCRIPTION CITY STATE ZIP
|
64
69
|
d = d.split(" ")
|
65
70
|
date = DateTime.parse(d[0..3].join(" "))
|
66
71
|
desc = d[4..d.length].join(" ")
|
67
|
-
|
72
|
+
# the zip code is always the last word, if it is all numbers
|
73
|
+
if use_city_state_lookup then
|
74
|
+
# this gets the exact location of the package, and is very accurate,
|
75
|
+
# however, it requires access to the shipping services in USPS
|
76
|
+
zip = d[d.length-1]
|
77
|
+
loc = ""
|
78
|
+
# check if zip is a number
|
79
|
+
if zip.to_i.to_s == zip
|
80
|
+
loc = city_state_lookup(zip)
|
81
|
+
loc = "#{loc[:city].titelize}, #{loc[:state]} #{loc[:zip]}"
|
82
|
+
# attempt to delete the location from the description
|
83
|
+
desc = desc.gsub("#{loc[:city]} #{loc[:state]} #{loc[:zip]}", "")
|
84
|
+
end
|
85
|
+
else
|
86
|
+
# extract the location from the description - not always accurate,
|
87
|
+
# but better than nothing
|
88
|
+
d = desc.split(" ") # => ['the', 'description', 'city', 'state', 'zip']
|
89
|
+
desc = d[0..d.length-4].join(" ") # => "the description"
|
90
|
+
loc = d[d.length-3, d.length] # => ['city', 'state', 'zip']
|
91
|
+
loc = "#{loc[0].titleize}, #{loc[1]} #{loc[2]}" # "City, STATE zip"
|
92
|
+
end
|
93
|
+
events << Trackerific::Event.new(
|
94
|
+
:date => date,
|
95
|
+
:description => desc.capitalize,
|
96
|
+
:location => loc
|
97
|
+
)
|
68
98
|
end unless tracking_info['TrackDetail'].nil?
|
69
99
|
# return the details
|
70
100
|
Trackerific::Details.new(
|
71
|
-
tracking_info['ID'],
|
72
|
-
tracking_info['TrackSummary'],
|
73
|
-
|
101
|
+
:package_id => tracking_info['ID'],
|
102
|
+
:summary => tracking_info['TrackSummary'],
|
103
|
+
:events => events
|
74
104
|
)
|
75
105
|
end
|
76
106
|
|
107
|
+
# Gets the city/state of a zipcode
|
108
|
+
# @param [String] zipcode The zipcode to find the city/state for
|
109
|
+
# @return [Hash] { zip: 'the zipcode, 'city: "the city", state: "the state" }
|
110
|
+
# @example Lookup zipcode for Beverly Hills, CA
|
111
|
+
# usps = Trackerific::USPS.new :user_id => 'youruserid'
|
112
|
+
# city_state = usps.city_state_lookup(90210)
|
113
|
+
# city_state[:city] # => BEVERLY HILLS
|
114
|
+
# city_state[:state] # => CA
|
115
|
+
# city_state[:zip] # => 90210
|
116
|
+
# @api public
|
117
|
+
def city_state_lookup(zipcode)
|
118
|
+
response = self.class.get(
|
119
|
+
Rails.env.production? ? "/ShippingAPI.dll" : "/ShippingAPITest.dll",
|
120
|
+
:query => {
|
121
|
+
:API => 'CityStateLookup',
|
122
|
+
:XML => build_city_state_xml_request(zipcode)
|
123
|
+
}.to_query
|
124
|
+
)
|
125
|
+
# raise any errors
|
126
|
+
error = check_response_for_errors(response, :CityStateLookup)
|
127
|
+
raise error unless error.nil?
|
128
|
+
# return the city, state, and zip
|
129
|
+
response = response['CityStateLookupResponse']['ZipCode']
|
130
|
+
{
|
131
|
+
:city => response['City'],
|
132
|
+
:state => response['State'],
|
133
|
+
:zip => response['Zip5']
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
77
137
|
protected
|
78
138
|
|
79
|
-
#
|
139
|
+
# Checks a HTTParty response for USPS, or HTTP errors
|
140
|
+
# @param [HTTParty::Response] response The HTTParty response to check
|
141
|
+
# @return The exception to raise, or nil
|
142
|
+
# @api private
|
143
|
+
def check_response_for_errors(response, api)
|
144
|
+
# return any HTTP errors
|
145
|
+
return response.error unless response.code == 200
|
146
|
+
# return a Trackerific::Error if there is an error in the response, or if
|
147
|
+
# the tracking response is malformed
|
148
|
+
return Trackerific::Error.new(response['Error']['Description']) unless response['Error'].nil?
|
149
|
+
return Trackerific::Error.new("Tracking information not found in response from server.") if response['TrackResponse'].nil? && api == :TrackV2
|
150
|
+
return Trackerific::Error.new("City / state information not found in response from server.") if response['CityStateLookupResponse'].nil? && api == :CityStateLookup
|
151
|
+
return nil # no errors to report
|
152
|
+
end
|
153
|
+
|
154
|
+
# Builds an XML city/state lookup request
|
155
|
+
# @param [String] zipcode The zipcode to find the city/state for
|
156
|
+
# @return [String] the xml request
|
157
|
+
# @api private
|
158
|
+
def build_city_state_xml_request(zipcode)
|
159
|
+
xml = ""
|
160
|
+
# set up the Builder
|
161
|
+
builder = ::Builder::XmlMarkup.new(:target => xml)
|
162
|
+
# add the XML header
|
163
|
+
builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
|
164
|
+
# build the request
|
165
|
+
builder.CityStateLookupRequest(:USERID => @options[:user_id]) do |request|
|
166
|
+
request.ZipCode(:ID => "5") do |zip|
|
167
|
+
zip.Zip5 zipcode
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Builds an XML tracking request
|
80
173
|
# @return [String] the xml request
|
81
174
|
# @api private
|
82
|
-
def
|
175
|
+
def build_tracking_xml_request
|
83
176
|
xml = ""
|
84
177
|
# set up the Builder
|
85
178
|
builder = ::Builder::XmlMarkup.new(:target => xml)
|