trackerific 0.5.5 → 0.6.0
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 +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)
|