Class: ReactiveShipping::Carrier

Inherits:
Object
  • Object
show all
Includes:
ActiveUtils::PostsData, ActiveUtils::RequiresParameters
Defined in:
lib/reactive_shipping/carrier.rb

Overview

Carrier is the abstract base class for all supported carriers.

To implement support for a carrier, you should subclass this class and implement all the methods that the carrier supports.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Carrier

Credentials should be in options hash under keys :login, :password and/or :key.

Parameters:

  • options (Hash) (defaults to: {})

    The details needed to connect to the carrier's API.

Options Hash (options):

  • :test (Boolean)

    Set this to true to connect to the carrier's sandbox environment instead of the production environment.



29
30
31
32
33
34
# File 'lib/reactive_shipping/carrier.rb', line 29

def initialize(options = {})
  requirements.each { |key| requires!(options, key) }
  @options = options
  @last_request = nil
  @test_mode = @options[:test]
end

Instance Attribute Details

#last_requestObject

The last request performed against the carrier's API.

See Also:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/reactive_shipping/carrier.rb', line 20

class Carrier
  attr_reader :last_request
  attr_accessor :test_mode
  alias_method :test_mode?, :test_mode

  # Credentials should be in options hash under keys :login, :password and/or :key.
  # @param options [Hash] The details needed to connect to the carrier's API.
  # @option options [Boolean] :test Set this to true to connect to the carrier's
  #   sandbox environment instead of the production environment.
  def initialize(options = {})
    requirements.each { |key| requires!(options, key) }
    @options = options
    @last_request = nil
    @test_mode = @options[:test]
  end

  # Asks the carrier for rate estimates for a given shipment.
  #
  # @note Override with whatever you need to get the rates from the carrier.
  #
  # @param origin [ReactiveShipping::Location] Where the shipment will originate from.
  # @param destination [ReactiveShipping::Location] Where the package will go.
  # @param packages [Array<ReactiveShipping::Package>] The list of packages that will
  #   be in the shipment.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::RateResponse] The response from the carrier, which
  #   includes 0 or more rate estimates for different shipping products
  def find_rates(origin, destination, packages, options = {})
    raise NotImplementedError, "#find_rates is not supported by #{self.class.name}."
  end

  # Registers a new shipment with the carrier, to get a tracking number and
  # potentially shipping labels
  #
  # @note Override with whatever you need to register a shipment, and obtain
  #   shipping labels if supported by the carrier.
  #
  # @param origin [ReactiveShipping::Location] Where the shipment will originate from.
  # @param destination [ReactiveShipping::Location] Where the package will go.
  # @param packages [Array<ReactiveShipping::Package>] The list of packages that will
  #   be in the shipment.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::ShipmentResponse] The response from the carrier. This
  #   response should include a shipment identifier or tracking_number if successful,
  #   and potentially shipping labels.
  def create_shipment(origin, destination, packages, options = {})
    raise NotImplementedError, "#create_shipment is not supported by #{self.class.name}."
  end

  # Cancels a shipment with a carrier.
  #
  # @note Override with whatever you need to cancel a shipping label
  #
  # @param shipment_id [String] The unique identifier of the shipment to cancel.
  #  This can be shipment_id or tracking number depending on carrier. Up to you and
  #  the carrier
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::ShipmentResponse] The response from the carrier. This
  #   response in most cases has a cancellation id.
  def cancel_shipment(shipment_id, options = {})
    raise NotImplementedError, "#cancel_shipment is not supported by #{self.class.name}."
  end

  # Retrieves tracking information for a previous shipment
  #
  # @note Override with whatever you need to get a shipping label
  #
  # @param tracking_number [String] The unique identifier of the shipment to track.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::TrackingResponse] The response from the carrier. This
  #   response should a list of shipment tracking events if successful.
  def find_tracking_info(tracking_number, options = {})
    raise NotImplementedError, "#find_tracking_info is not supported by #{self.class.name}."
  end

  # Get a list of services available for the a specific route
  #
  # @param origin_country_code [String] The country of origin
  # @param destination_country_code [String] The destination country
  # @return [Array<String>] A list of names of the available services
  #
  def available_services(origin_country_code, destination_country_code, options = {})
    raise NotImplementedError, "#available_services is not supported by #{self.class.name}."
  end

  # Validate credentials with a call to the API.
  #
  # By default this just does a `find_rates` call with the origin and destination both as
  # the carrier's default_location. Override to provide alternate functionality, such as
  # checking for `test_mode` to use test servers, etc.
  #
  # @return [Boolean] Should return `true` if the provided credentials proved to work,
  #   `false` otherswise.
  def valid_credentials?
    location = self.class.default_location
    find_rates(location, location, Package.new(100, [5, 15, 30]), :test => test_mode)
  rescue ReactiveShipping::ResponseError
    false
  else
    true
  end

  # The maximum weight the carrier will accept.
  # @return [Measured::Weight]
  def maximum_weight
    Measured::Weight.new(150, :pounds)
  end

  # The address field maximum length accepted by the carrier
  # @return [Integer]
  def maximum_address_field_length
    255
  end

  protected

  include ActiveUtils::RequiresParameters
  include ActiveUtils::PostsData

  # Returns the keys that are required to be passed to the options hash
  # @note Override to return required keys in options hash for initialize method.
  # @return [Array<Symbol>]
  def requirements
    []
  end

  # The default location to use for {#valid_credentials?}.
  # @note Override for non-U.S.-based carriers.
  # @return [ReactiveShipping::Location]
  def self.default_location
    Location.new( :country => 'US',
                  :state => 'CA',
                  :city => 'Beverly Hills',
                  :address1 => '455 N. Rexford Dr.',
                  :address2 => '3rd Floor',
                  :zip => '90210',
                  :phone => '1-310-285-1013',
                  :fax => '1-310-275-8159')
  end

  # Use after building the request to save for later inspection.
  # @return [void]
  def save_request(r)
    @last_request = r
  end

  # Calculates a timestamp that corresponds a given number of business days in the future
  #
  # @param days [Integer] The number of business days from now.
  # @return [DateTime] A timestamp, the provided number of business days in the future.
  def timestamp_from_business_day(days)
    return unless days
    date = DateTime.now.utc

    days.times do
      date += 1.day

      date += 2.days if date.saturday?
      date += 1.day if date.sunday?
    end

    date.to_datetime
  end
end

#test_modeBoolean Also known as: test_mode?

Whether to interact with the carrier's sandbox environment.

Returns:

  • (Boolean)


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/reactive_shipping/carrier.rb', line 20

class Carrier
  attr_reader :last_request
  attr_accessor :test_mode
  alias_method :test_mode?, :test_mode

  # Credentials should be in options hash under keys :login, :password and/or :key.
  # @param options [Hash] The details needed to connect to the carrier's API.
  # @option options [Boolean] :test Set this to true to connect to the carrier's
  #   sandbox environment instead of the production environment.
  def initialize(options = {})
    requirements.each { |key| requires!(options, key) }
    @options = options
    @last_request = nil
    @test_mode = @options[:test]
  end

  # Asks the carrier for rate estimates for a given shipment.
  #
  # @note Override with whatever you need to get the rates from the carrier.
  #
  # @param origin [ReactiveShipping::Location] Where the shipment will originate from.
  # @param destination [ReactiveShipping::Location] Where the package will go.
  # @param packages [Array<ReactiveShipping::Package>] The list of packages that will
  #   be in the shipment.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::RateResponse] The response from the carrier, which
  #   includes 0 or more rate estimates for different shipping products
  def find_rates(origin, destination, packages, options = {})
    raise NotImplementedError, "#find_rates is not supported by #{self.class.name}."
  end

  # Registers a new shipment with the carrier, to get a tracking number and
  # potentially shipping labels
  #
  # @note Override with whatever you need to register a shipment, and obtain
  #   shipping labels if supported by the carrier.
  #
  # @param origin [ReactiveShipping::Location] Where the shipment will originate from.
  # @param destination [ReactiveShipping::Location] Where the package will go.
  # @param packages [Array<ReactiveShipping::Package>] The list of packages that will
  #   be in the shipment.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::ShipmentResponse] The response from the carrier. This
  #   response should include a shipment identifier or tracking_number if successful,
  #   and potentially shipping labels.
  def create_shipment(origin, destination, packages, options = {})
    raise NotImplementedError, "#create_shipment is not supported by #{self.class.name}."
  end

  # Cancels a shipment with a carrier.
  #
  # @note Override with whatever you need to cancel a shipping label
  #
  # @param shipment_id [String] The unique identifier of the shipment to cancel.
  #  This can be shipment_id or tracking number depending on carrier. Up to you and
  #  the carrier
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::ShipmentResponse] The response from the carrier. This
  #   response in most cases has a cancellation id.
  def cancel_shipment(shipment_id, options = {})
    raise NotImplementedError, "#cancel_shipment is not supported by #{self.class.name}."
  end

  # Retrieves tracking information for a previous shipment
  #
  # @note Override with whatever you need to get a shipping label
  #
  # @param tracking_number [String] The unique identifier of the shipment to track.
  # @param options [Hash] Carrier-specific parameters.
  # @return [ReactiveShipping::TrackingResponse] The response from the carrier. This
  #   response should a list of shipment tracking events if successful.
  def find_tracking_info(tracking_number, options = {})
    raise NotImplementedError, "#find_tracking_info is not supported by #{self.class.name}."
  end

  # Get a list of services available for the a specific route
  #
  # @param origin_country_code [String] The country of origin
  # @param destination_country_code [String] The destination country
  # @return [Array<String>] A list of names of the available services
  #
  def available_services(origin_country_code, destination_country_code, options = {})
    raise NotImplementedError, "#available_services is not supported by #{self.class.name}."
  end

  # Validate credentials with a call to the API.
  #
  # By default this just does a `find_rates` call with the origin and destination both as
  # the carrier's default_location. Override to provide alternate functionality, such as
  # checking for `test_mode` to use test servers, etc.
  #
  # @return [Boolean] Should return `true` if the provided credentials proved to work,
  #   `false` otherswise.
  def valid_credentials?
    location = self.class.default_location
    find_rates(location, location, Package.new(100, [5, 15, 30]), :test => test_mode)
  rescue ReactiveShipping::ResponseError
    false
  else
    true
  end

  # The maximum weight the carrier will accept.
  # @return [Measured::Weight]
  def maximum_weight
    Measured::Weight.new(150, :pounds)
  end

  # The address field maximum length accepted by the carrier
  # @return [Integer]
  def maximum_address_field_length
    255
  end

  protected

  include ActiveUtils::RequiresParameters
  include ActiveUtils::PostsData

  # Returns the keys that are required to be passed to the options hash
  # @note Override to return required keys in options hash for initialize method.
  # @return [Array<Symbol>]
  def requirements
    []
  end

  # The default location to use for {#valid_credentials?}.
  # @note Override for non-U.S.-based carriers.
  # @return [ReactiveShipping::Location]
  def self.default_location
    Location.new( :country => 'US',
                  :state => 'CA',
                  :city => 'Beverly Hills',
                  :address1 => '455 N. Rexford Dr.',
                  :address2 => '3rd Floor',
                  :zip => '90210',
                  :phone => '1-310-285-1013',
                  :fax => '1-310-275-8159')
  end

  # Use after building the request to save for later inspection.
  # @return [void]
  def save_request(r)
    @last_request = r
  end

  # Calculates a timestamp that corresponds a given number of business days in the future
  #
  # @param days [Integer] The number of business days from now.
  # @return [DateTime] A timestamp, the provided number of business days in the future.
  def timestamp_from_business_day(days)
    return unless days
    date = DateTime.now.utc

    days.times do
      date += 1.day

      date += 2.days if date.saturday?
      date += 1.day if date.sunday?
    end

    date.to_datetime
  end
end

Class Method Details

.default_locationReactiveShipping::Location (protected)

Note:

Override for non-U.S.-based carriers.

The default location to use for #valid_credentials?.



149
150
151
152
153
154
155
156
157
158
# File 'lib/reactive_shipping/carrier.rb', line 149

def self.default_location
  Location.new( :country => 'US',
                :state => 'CA',
                :city => 'Beverly Hills',
                :address1 => '455 N. Rexford Dr.',
                :address2 => '3rd Floor',
                :zip => '90210',
                :phone => '1-310-285-1013',
                :fax => '1-310-275-8159')
end

Instance Method Details

#available_services(origin_country_code, destination_country_code, options = {}) ⇒ Array<String>

Get a list of services available for the a specific route

Parameters:

  • origin_country_code (String)

    The country of origin

  • destination_country_code (String)

    The destination country

Returns:

  • (Array<String>)

    A list of names of the available services

Raises:

  • (NotImplementedError)


101
102
103
# File 'lib/reactive_shipping/carrier.rb', line 101

def available_services(origin_country_code, destination_country_code, options = {})
  raise NotImplementedError, "#available_services is not supported by #{self.class.name}."
end

#cancel_shipment(shipment_id, options = {}) ⇒ ReactiveShipping::ShipmentResponse

Note:

Override with whatever you need to cancel a shipping label

Cancels a shipment with a carrier.

Parameters:

  • shipment_id (String)

    The unique identifier of the shipment to cancel. This can be shipment_id or tracking number depending on carrier. Up to you and the carrier

  • options (Hash) (defaults to: {})

    Carrier-specific parameters.

Returns:

  • (ReactiveShipping::ShipmentResponse)

    The response from the carrier. This response in most cases has a cancellation id.

Raises:

  • (NotImplementedError)


79
80
81
# File 'lib/reactive_shipping/carrier.rb', line 79

def cancel_shipment(shipment_id, options = {})
  raise NotImplementedError, "#cancel_shipment is not supported by #{self.class.name}."
end

#create_shipment(origin, destination, packages, options = {}) ⇒ ReactiveShipping::ShipmentResponse

Note:

Override with whatever you need to register a shipment, and obtain shipping labels if supported by the carrier.

Registers a new shipment with the carrier, to get a tracking number and potentially shipping labels

Parameters:

Returns:

  • (ReactiveShipping::ShipmentResponse)

    The response from the carrier. This response should include a shipment identifier or tracking_number if successful, and potentially shipping labels.

Raises:

  • (NotImplementedError)


65
66
67
# File 'lib/reactive_shipping/carrier.rb', line 65

def create_shipment(origin, destination, packages, options = {})
  raise NotImplementedError, "#create_shipment is not supported by #{self.class.name}."
end

#find_rates(origin, destination, packages, options = {}) ⇒ ReactiveShipping::RateResponse

Note:

Override with whatever you need to get the rates from the carrier.

Asks the carrier for rate estimates for a given shipment.

Parameters:

Returns:

Raises:

  • (NotImplementedError)


47
48
49
# File 'lib/reactive_shipping/carrier.rb', line 47

def find_rates(origin, destination, packages, options = {})
  raise NotImplementedError, "#find_rates is not supported by #{self.class.name}."
end

#find_tracking_info(tracking_number, options = {}) ⇒ ReactiveShipping::TrackingResponse

Note:

Override with whatever you need to get a shipping label

Retrieves tracking information for a previous shipment

Parameters:

  • tracking_number (String)

    The unique identifier of the shipment to track.

  • options (Hash) (defaults to: {})

    Carrier-specific parameters.

Returns:

Raises:

  • (NotImplementedError)


91
92
93
# File 'lib/reactive_shipping/carrier.rb', line 91

def find_tracking_info(tracking_number, options = {})
  raise NotImplementedError, "#find_tracking_info is not supported by #{self.class.name}."
end

#maximum_address_field_lengthInteger

The address field maximum length accepted by the carrier

Returns:

  • (Integer)


130
131
132
# File 'lib/reactive_shipping/carrier.rb', line 130

def maximum_address_field_length
  255
end

#maximum_weightMeasured::Weight

The maximum weight the carrier will accept.

Returns:

  • (Measured::Weight)


124
125
126
# File 'lib/reactive_shipping/carrier.rb', line 124

def maximum_weight
  Measured::Weight.new(150, :pounds)
end

#requirementsArray<Symbol> (protected)

Note:

Override to return required keys in options hash for initialize method.

Returns the keys that are required to be passed to the options hash

Returns:

  • (Array<Symbol>)


142
143
144
# File 'lib/reactive_shipping/carrier.rb', line 142

def requirements
  []
end

#save_request(r) (protected)

This method returns an undefined value.

Use after building the request to save for later inspection.



162
163
164
# File 'lib/reactive_shipping/carrier.rb', line 162

def save_request(r)
  @last_request = r
end

#timestamp_from_business_day(days) ⇒ DateTime (protected)

Calculates a timestamp that corresponds a given number of business days in the future

Parameters:

  • days (Integer)

    The number of business days from now.

Returns:

  • (DateTime)

    A timestamp, the provided number of business days in the future.



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/reactive_shipping/carrier.rb', line 170

def timestamp_from_business_day(days)
  return unless days
  date = DateTime.now.utc

  days.times do
    date += 1.day

    date += 2.days if date.saturday?
    date += 1.day if date.sunday?
  end

  date.to_datetime
end

#valid_credentials?Boolean

Validate credentials with a call to the API.

By default this just does a find_rates call with the origin and destination both as the carrier's default_location. Override to provide alternate functionality, such as checking for test_mode to use test servers, etc.

Returns:

  • (Boolean)

    Should return true if the provided credentials proved to work, false otherswise.



113
114
115
116
117
118
119
120
# File 'lib/reactive_shipping/carrier.rb', line 113

def valid_credentials?
  location = self.class.default_location
  find_rates(location, location, Package.new(100, [5, 15, 30]), :test => test_mode)
rescue ReactiveShipping::ResponseError
  false
else
  true
end