trackerific 0.6.2 → 0.7.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +1 -2
  4. data/Gemfile +3 -22
  5. data/Gemfile.lock +33 -107
  6. data/README.rdoc +35 -61
  7. data/Rakefile +10 -36
  8. data/lib/trackerific/configuration.rb +3 -46
  9. data/lib/trackerific/details.rb +2 -71
  10. data/lib/trackerific/event.rb +5 -49
  11. data/lib/trackerific/services/base.rb +45 -0
  12. data/lib/trackerific/services/fedex.rb +75 -85
  13. data/lib/trackerific/services/mock_service.rb +43 -54
  14. data/lib/trackerific/services/ups.rb +94 -103
  15. data/lib/trackerific/services/usps.rb +182 -186
  16. data/lib/trackerific/services.rb +41 -0
  17. data/lib/trackerific/version.rb +3 -0
  18. data/lib/trackerific.rb +30 -73
  19. data/spec/fixtures/{fedex_error_response.xml → fedex/error.xml} +0 -0
  20. data/spec/fixtures/{fedex_success_response.xml → fedex/success.xml} +0 -0
  21. data/spec/fixtures/{ups_error_response.xml → ups/error.xml} +0 -0
  22. data/spec/fixtures/{ups_success_response.xml → ups/success.xml} +0 -0
  23. data/spec/fixtures/{usps_city_state_lookup_response.xml → usps/city_state_lookup.xml} +0 -0
  24. data/spec/fixtures/{usps_error_response.xml → usps/error.xml} +0 -0
  25. data/spec/fixtures/{usps_success_response.xml → usps/success.xml} +0 -0
  26. data/spec/lib/trackerific/configuration_spec.rb +8 -39
  27. data/spec/lib/trackerific/details_spec.rb +13 -78
  28. data/spec/lib/trackerific/error_spec.rb +1 -5
  29. data/spec/lib/trackerific/event_spec.rb +14 -43
  30. data/spec/lib/trackerific/services/base_spec.rb +13 -0
  31. data/spec/lib/trackerific/services/fedex_spec.rb +59 -62
  32. data/spec/lib/trackerific/services/mock_service_spec.rb +33 -46
  33. data/spec/lib/trackerific/services/ups_spec.rb +46 -66
  34. data/spec/lib/trackerific/services/usps_spec.rb +80 -94
  35. data/spec/lib/trackerific/services_spec.rb +37 -0
  36. data/spec/lib/trackerific/version_spec.rb +5 -0
  37. data/spec/lib/trackerific_spec.rb +15 -53
  38. data/spec/spec_helper.rb +2 -9
  39. data/spec/support/fixtures.rb +4 -18
  40. data/spec/support/test_services.rb +23 -0
  41. data/trackerific.gemspec +24 -102
  42. metadata +151 -185
  43. data/VERSION +0 -1
  44. data/changelog +0 -9
  45. data/examples/custom_service_spec.rb +0 -44
  46. data/lib/helpers/options_helper.rb +0 -23
  47. data/lib/trackerific/service.rb +0 -100
  48. data/spec/lib/helpers/options_helper_spec.rb +0 -82
  49. data/spec/lib/trackerific/service_spec.rb +0 -44
  50. data/spec/support/trackerific.rb +0 -21
@@ -1,101 +1,91 @@
1
1
  require 'httparty'
2
+ require 'builder'
2
3
 
3
4
  module Trackerific
4
-
5
- # Provides package tracking support for FedEx
6
- class FedEx < Trackerific::Service
7
- # setup HTTParty
8
- include ::HTTParty
9
- format :xml
10
- base_uri "https://gateway.fedex.com"
11
-
12
- class << self
5
+ module Services
6
+ class FedEx < Base
7
+ include ::HTTParty
8
+
9
+ format :xml
10
+ base_uri "https://gateway.fedex.com"
11
+
12
+ self.register :fedex
13
+
13
14
  # An Array of Regexp that matches valid FedEx package IDs
14
15
  # @return [Array, Regexp] the regular expression
15
16
  # @api private
16
- def package_id_matchers
17
+ def self.package_id_matchers
17
18
  [ /^[0-9]{15}$/ ]
18
19
  end
19
-
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
- # and :meter
23
- # @api private
24
- def required_parameters
25
- [:account, :meter]
20
+
21
+ def initialize(options={})
22
+ @options = options
26
23
  end
27
- end
28
-
29
- # Tracks a FedEx package
30
- # @param [String] package_id the package identifier
31
- # @return [Trackerific::Details] the tracking details
32
- # @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
33
- # @example Track a package
34
- # fedex = Trackerific::FedEx.new :account => 'account', :meter => 'meter'
35
- # details = fedex.track_package("183689015000001")
36
- # @api public
37
- def track_package(package_id)
38
- super
39
- # request tracking information from FedEx via HTTParty
40
- http_response = self.class.post "/GatewayDC", :body => build_xml_request
41
- # raise any HTTP errors
42
- http_response.error! unless http_response.code == 200
43
- # get the tracking information from the reply
44
- track_reply = http_response["FDXTrack2Reply"]
45
- # raise a Trackerific::Error if there is an error in the reply
46
- raise Trackerific::Error, track_reply["Error"]["Message"] unless track_reply["Error"].nil?
47
- # get the details from the reply
48
- details = track_reply["Package"]
49
- # convert them into Trackerific::Events
50
- events = []
51
- details["Event"].each do |e|
52
- date = Time.parse("#{e["Date"]} #{e["Time"]}")
53
- desc = e["Description"]
54
- addr = e["Address"]
55
- events << Trackerific::Event.new(
56
- :date => date,
57
- :description => desc,
58
- :location => "#{addr["StateOrProvinceCode"]} #{addr["PostalCode"]}"
24
+
25
+ # Tracks a FedEx package
26
+ # @param [String] package_id the package identifier
27
+ # @return [Trackerific::Details] the tracking details
28
+ # @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
29
+ # @example Track a package
30
+ # fedex = Trackerific::FedEx.new :account => 'account', :meter => 'meter'
31
+ # details = fedex.track_package("183689015000001")
32
+ # @api public
33
+ def track(package_id)
34
+ # request tracking information from FedEx via HTTParty
35
+ http_response = self.class.post "/GatewayDC", body: build_xml_request
36
+ # raise any HTTP errors
37
+ http_response.error! unless http_response.code == 200
38
+ # get the tracking information from the reply
39
+ track_reply = http_response["FDXTrack2Reply"]
40
+ # raise a Trackerific::Error if there is an error in the reply
41
+ raise Trackerific::Error, track_reply["Error"]["Message"] unless track_reply["Error"].nil?
42
+ # get the details from the reply
43
+ details = track_reply["Package"]
44
+ # convert them into Trackerific::Events
45
+ events = []
46
+ details["Event"].each do |e|
47
+ date = Time.parse("#{e["Date"]} #{e["Time"]}")
48
+ desc = e["Description"]
49
+ addr = e["Address"]
50
+ loc = "#{addr["StateOrProvinceCode"]} #{addr["PostalCode"]}"
51
+ events << Trackerific::Event.new(date, desc, loc)
52
+ end
53
+ # Return a Trackerific::Details containing all the events
54
+ Trackerific::Details.new(
55
+ details["TrackingNumber"], details["StatusDescription"], events
59
56
  )
60
57
  end
61
- # Return a Trackerific::Details containing all the events
62
- Trackerific::Details.new(
63
- :package_id => details["TrackingNumber"],
64
- :summary => details["StatusDescription"],
65
- :events => events
66
- )
67
- end
68
-
69
- protected
70
-
71
- # Builds the XML request to send to FedEx
72
- # @return [String] a FDXTrack2Request XML
73
- # @api private
74
- def build_xml_request
75
- xml = ""
76
- # the API namespace
77
- xmlns_api = "http://www.fedex.com/fsmapi"
78
- # the XSI namespace
79
- xmlns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
80
- # the XSD namespace
81
- xsi_noNSL = "FDXTrack2Request.xsd"
82
- # create a new Builder to generate the XML
83
- builder = ::Builder::XmlMarkup.new(:target => xml)
84
- # add the XML header
85
- builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
86
- # Build, and return the request
87
- builder.FDXTrack2Request "xmlns:api"=>xmlns_api, "xmlns:xsi"=>xmlns_xsi, "xsi:noNamespaceSchemaLocation" => xsi_noNSL do |r|
88
- r.RequestHeader do |rh|
89
- rh.AccountNumber @options[:account]
90
- rh.MeterNumber @options[:meter]
91
- end
92
- r.PackageIdentifier do |pi|
93
- pi.Value @package_id
58
+
59
+ private
60
+
61
+ # Builds the XML request to send to FedEx
62
+ # @return [String] a FDXTrack2Request XML
63
+ # @api private
64
+ def build_xml_request
65
+ xml = ""
66
+ # the API namespace
67
+ xmlns_api = "http://www.fedex.com/fsmapi"
68
+ # the XSI namespace
69
+ xmlns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
70
+ # the XSD namespace
71
+ xsi_noNSL = "FDXTrack2Request.xsd"
72
+ # create a new Builder to generate the XML
73
+ builder = ::Builder::XmlMarkup.new(:target => xml)
74
+ # add the XML header
75
+ builder.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
76
+ # Build, and return the request
77
+ builder.FDXTrack2Request "xmlns:api"=>xmlns_api, "xmlns:xsi"=>xmlns_xsi, "xsi:noNamespaceSchemaLocation" => xsi_noNSL do |r|
78
+ r.RequestHeader do |rh|
79
+ rh.AccountNumber @options[:account]
80
+ rh.MeterNumber @options[:meter]
81
+ end
82
+ r.PackageIdentifier do |pi|
83
+ pi.Value @package_id
84
+ end
85
+ r.DetailScans true
94
86
  end
95
- r.DetailScans true
87
+ xml
96
88
  end
97
- xml
98
89
  end
99
-
100
90
  end
101
91
  end
@@ -1,62 +1,51 @@
1
- require 'date'
2
-
3
1
  module Trackerific
4
-
5
- # Provides a mock service for using in test and development
6
- class MockService < Trackerific::Service
7
-
8
- class << self
2
+ module Services
3
+ # Provides a mock service for using in test and development
4
+ class MockService < Base
5
+ require 'date'
6
+
7
+ self.register :mock_service
8
+
9
9
  # Regular expression matchers for mocked Trackerific service
10
10
  # @return [Array, Regexp] the regular expression
11
11
  # @api private
12
- def package_id_matchers
13
- return [ /XXXXXXXXXX/, /XXXxxxxxxx/ ] unless Rails.env.production?
14
- return [ ]
12
+ def self.package_id_matchers
13
+ [ /XXXXXXXXXX/, /XXXxxxxxxx/ ]
15
14
  end
16
-
17
- # Returns an Array of required parameters used when creating a new instance
18
- # @return [Array] required parameters
19
- # @api private
20
- def required_parameters
21
- [ ]
22
- end
23
- end
24
-
25
- # Sets up a mocked package details
26
- # @param [String] package_id the package identifier
27
- # @return [Trackerific::Details] the tracking details
28
- # @raise [Trackerific::Error] raised when the server returns an error
29
- # @example Track a package
30
- # service = Trackerific::MockedService.new
31
- # details = service.track_package("XXXXXXXXXX") # => valid response
32
- # details = service.track_package("XXXxxxxxxx") # => throws a Trackerific::Error exception
33
- # @api public
34
- def track_package(package_id)
35
- super
36
- if package_id == "XXXXXXXXXX"
37
- Trackerific::Details.new(
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
- )
56
- ]
57
- )
58
- else
59
- raise Trackerific::Error, "Package not found."
15
+
16
+ # Sets up a mocked package details
17
+ # @param [String] package_id the package identifier
18
+ # @return [Trackerific::Details] the tracking details
19
+ # @raise [Trackerific::Error] raised when the server returns an error
20
+ # @example Track a package
21
+ # service = Trackerific::Services::MockedService.new
22
+ # details = service.track("XXXXXXXXXX") # => valid response
23
+ # details = service.track("XXXxxxxxxx") # => throws a Trackerific::Error exception
24
+ # @api public
25
+ def track(id)
26
+ if id == "XXXXXXXXXX"
27
+ Trackerific::Details.new(id, "Your package was delivered.",
28
+ [
29
+ Trackerific::Event.new(
30
+ :date => Date.today,
31
+ :description => "Package delivered.",
32
+ :location => "SANTA MARIA, CA"
33
+ ),
34
+ Trackerific::Event.new(
35
+ :date => Date.today - 1,
36
+ :description => "Package scanned.",
37
+ :location => "SANTA BARBARA, CA"
38
+ ),
39
+ Trackerific::Event.new(
40
+ :date => Date.today - 2,
41
+ :description => "Package picked up for delivery.",
42
+ :location => "LOS ANGELES, CA"
43
+ )
44
+ ]
45
+ )
46
+ else
47
+ raise Trackerific::Error, "Package not found."
48
+ end
60
49
  end
61
50
  end
62
51
  end
@@ -1,120 +1,111 @@
1
+ require 'httparty'
2
+ require 'builder'
1
3
  require 'date'
2
4
 
3
5
  module Trackerific
4
- require 'httparty'
5
-
6
- # Provides package tracking support for UPS.
7
- class UPS < Trackerific::Service
8
- # setup HTTParty
9
- include ::HTTParty
10
- format :xml
11
- # use the test site for Rails development, production for everything else
12
- base_uri defined?(Rails) ? case Rails.env
13
- when 'test','development' then 'https://wwwcie.ups.com/ups.app/xml'
6
+ module Services
7
+ # Provides package tracking support for UPS.
8
+ class UPS < Base
9
+ self.register :ups
10
+
11
+ include ::HTTParty
12
+ format :xml
13
+
14
+ base_uri case (ENV['RAILS_ENV'] || 'production')
14
15
  when 'production' then 'https://www.ups.com/ups.app/xml'
15
- end : 'https://www.ups.com/ups.app/xml'
16
-
17
- class << self
16
+ else 'https://wwwcie.ups.com/ups.app/xml'
17
+ end
18
+
19
+ def initialize(options={})
20
+ @options = options
21
+ end
22
+
18
23
  # An Array of Regexp that matches valid UPS package IDs
19
24
  # @return [Array, Regexp] the regular expression
20
25
  # @api private
21
- def package_id_matchers
26
+ def self.package_id_matchers
22
27
  [ /^.Z/, /^[HK].{10}$/ ]
23
- end
24
- # The required parameters for tracking a UPS package
25
- # @return [Array] the required parameters for tracking a UPS package
26
- # @api private
27
- def required_parameters
28
- [:key, :user_id, :password]
29
28
  end
30
- end
31
-
32
- # Tracks a UPS package
33
- # @param [String] package_id the package identifier
34
- # @return [Trackerific::Details] the tracking details
35
- # @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
36
- # @example Track a package
37
- # ups = Trackerific::UPS.new key: 'api key', user_id: 'user', password: 'secret'
38
- # details = ups.track_package("1Z12345E0291980793")
39
- # @api public
40
- def track_package(package_id)
41
- super
42
- # connect to UPS via HTTParty
43
- http_response = self.class.post('/Track', :body => build_xml_request)
44
- # throw any HTTP errors
45
- http_response.error! unless http_response.code == 200
46
- # Check the response for errors, return a Trackerific::Error, or parse
47
- # the response from UPS and return a Trackerific::Details
48
- case http_response['TrackResponse']['Response']['ResponseStatusCode']
49
- when "0" then raise Trackerific::Error, parse_error_response(http_response)
50
- when "1" then return parse_success_response(http_response)
51
- else raise Trackerific::Error, "Invalid response code returned from server."
29
+
30
+ # Tracks a UPS package
31
+ # @param [String] package_id the package identifier
32
+ # @return [Trackerific::Details] the tracking details
33
+ # @raise [Trackerific::Error] raised when the server returns an error (invalid credentials, tracking package, etc.)
34
+ # @example Track a package
35
+ # ups = Trackerific::UPS.new key: 'api key', user_id: 'user', password: 'secret'
36
+ # details = ups.track_package("1Z12345E0291980793")
37
+ # @api public
38
+ def track(id)
39
+ @package_id = id
40
+ # connect to UPS via HTTParty
41
+ http_response = self.class.post('/Track', body: build_xml_request)
42
+ # throw any HTTP errors
43
+ http_response.error! unless http_response.code == 200
44
+ # Check the response for errors, return a Trackerific::Error, or parse
45
+ # the response from UPS and return a Trackerific::Details
46
+ case http_response['TrackResponse']['Response']['ResponseStatusCode']
47
+ when "0" then raise Trackerific::Error, parse_error_response(http_response)
48
+ when "1" then return parse_success_response(http_response)
49
+ else raise Trackerific::Error, "Invalid response code returned from server."
50
+ end
52
51
  end
53
- end
54
-
55
- protected
56
-
57
- # Parses the response from UPS
58
- # @return [Trackerific::Details]
59
- # @api private
60
- def parse_success_response(http_response)
61
- # get the activity from the UPS response
62
- activity = http_response['TrackResponse']['Shipment']['Package']['Activity']
63
- # if there's only one activity in the list, we need to put it in an array
64
- activity = [activity] if activity.is_a? Hash
65
- # UPS does not provide a summary, so we'll just use the last tracking status
66
- summary = activity.first['Status']['StatusType']['Description'].titleize
67
- events = []
68
- activity.each do |a|
69
- # the time format from UPS is HHMMSS, which cannot be directly converted
70
- # to a Ruby time.
71
- hours = a['Time'][0..1]
72
- minutes = a['Time'][2..3]
73
- seconds = a['Time'][4..5]
74
- date = Date.parse(a['Date'])
75
- date = DateTime.parse("#{date} #{hours}:#{minutes}:#{seconds}")
76
- desc = a['Status']['StatusType']['Description'].titleize
77
- loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
78
- events << Trackerific::Event.new(
79
- :date => date,
80
- :description => desc,
81
- :location => loc
82
- )
52
+
53
+ protected
54
+
55
+ # Parses the response from UPS
56
+ # @return [Trackerific::Details]
57
+ # @api private
58
+ def parse_success_response(http_response)
59
+ # get the activity from the UPS response
60
+ activity = http_response['TrackResponse']['Shipment']['Package']['Activity']
61
+ # if there's only one activity in the list, we need to put it in an array
62
+ activity = [activity] if activity.is_a? Hash
63
+ # UPS does not provide a summary, so we'll just use the last tracking status
64
+ summary = activity.first['Status']['StatusType']['Description']
65
+ events = []
66
+ activity.each do |a|
67
+ # the time format from UPS is HHMMSS, which cannot be directly converted
68
+ # to a Ruby time.
69
+ hours = a['Time'][0..1]
70
+ minutes = a['Time'][2..3]
71
+ seconds = a['Time'][4..5]
72
+ date = Date.parse(a['Date'])
73
+ date = DateTime.parse("#{date} #{hours}:#{minutes}:#{seconds}")
74
+ desc = a['Status']['StatusType']['Description']
75
+ loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
76
+ events << Trackerific::Event.new(date, desc, loc)
77
+ end
78
+
79
+ Trackerific::Details.new(@package_id, summary, events)
83
80
  end
84
-
85
- Trackerific::Details.new(
86
- :package_id => @package_id,
87
- :summary => summary,
88
- :events => events
89
- )
90
- end
91
-
92
- # Parses a UPS tracking response, and returns any errors
93
- # @return [String] the UPS tracking error
94
- # @api private
95
- def parse_error_response(http_response)
96
- http_response['TrackResponse']['Response']['Error']['ErrorDescription']
97
- end
98
-
99
- # Builds the XML request to send to UPS for tracking a package
100
- # @return [String] the XML request
101
- # @api private
102
- def build_xml_request
103
- xml = ""
104
- builder = ::Builder::XmlMarkup.new(:target => xml)
105
- builder.AccessRequest do |ar|
106
- ar.AccessLicenseNumber @options[:key]
107
- ar.UserId @options[:user_id]
108
- ar.Password @options[:password]
81
+
82
+ # Parses a UPS tracking response, and returns any errors
83
+ # @return [String] the UPS tracking error
84
+ # @api private
85
+ def parse_error_response(http_response)
86
+ http_response['TrackResponse']['Response']['Error']['ErrorDescription']
109
87
  end
110
- builder.TrackRequest do |tr|
111
- tr.Request do |r|
112
- r.RequestAction 'Track'
113
- r.RequestOption 'activity'
88
+
89
+ # Builds the XML request to send to UPS for tracking a package
90
+ # @return [String] the XML request
91
+ # @api private
92
+ def build_xml_request
93
+ xml = ""
94
+ builder = ::Builder::XmlMarkup.new(:target => xml)
95
+ builder.AccessRequest do |ar|
96
+ ar.AccessLicenseNumber @options[:key]
97
+ ar.UserId @options[:user_id]
98
+ ar.Password @options[:password]
99
+ end
100
+ builder.TrackRequest do |tr|
101
+ tr.Request do |r|
102
+ r.RequestAction 'Track'
103
+ r.RequestOption 'activity'
104
+ end
105
+ tr.TrackingNumber @package_id
114
106
  end
115
- tr.TrackingNumber @package_id
107
+ return xml
116
108
  end
117
- return xml
118
109
  end
119
110
  end
120
111
  end