binarylogic-shippinglogic 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ spec/fedex_credentials.yml
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ben Johnson of Binary Logic
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,128 @@
1
+ = Shippinglogic
2
+
3
+ The goal of this library is to provide simple and straight forward interfaces to the various shipping web services: FedEx, UPS, USPS, etc. (Only FedEx is supported at this time)
4
+
5
+ == Helpful links
6
+
7
+ * <b>Documentation:</b> http://rdoc.info/projects/binarylogic/shippinglogic
8
+ * <b>Repository:</b> http://github.com/binarylogic/shippinglogic/tree/master
9
+ * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/16601-searchlogic
10
+ * <b>Google group:</b> http://groups.google.com/group/shippinglogic
11
+
12
+ <b>Before contacting me directly, please read:</b>
13
+
14
+ If you find a bug or a problem please post it on lighthouse. If you need help with something, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help. This also benefits other people in the future with the same questions / problems. Thank you.
15
+
16
+ == Install & use
17
+
18
+ Install the gem from rubyforge:
19
+
20
+ sudo gem install shippinglogic
21
+
22
+ Or from github:
23
+
24
+ sudo gem install binarylogic-shippinglogic
25
+
26
+ Now just include it in your project and you are ready to go.
27
+
28
+ You can also install this as a plugin:
29
+
30
+ script/plugin install git://github.com/binarylogic/shippinglogic.git
31
+
32
+ See below for usage examples.
33
+
34
+ == Usage
35
+
36
+ What's unique about this library is it's usage / syntax. Let me show you...
37
+
38
+ === Calls to the web services are lazy
39
+
40
+ A simple example where we want to track a FedEx package:
41
+
42
+ fedex = Shippinglogic::FedEx.new(key, password, account, meter) # all credentials FedEx requires
43
+ tracking = fedex.track(:tracking_number => "077973360403984")
44
+
45
+ Now here is why this is cool. Nothing has happened yet. No call has been made to the FedEx servers, etc. We do this because we don't need it yet. You don't need it until you start using the object. Lets continue with our example:
46
+
47
+ tracking.first # call is made to FedEx and returns the first tracking result in the array
48
+ tracking.size # cache is used to determine the size of the array
49
+
50
+ This is similar to how ActiveRecord's association proxies work. When you call user.orders no database activity occurs until you actually use the object.
51
+
52
+ === Flexibility
53
+
54
+ You will notice above we assign the result of the 'track' method to a variable called 'tracking'. That object has more to it:
55
+
56
+ # Initializing
57
+ tracking = fedex.track(:tracking_number => "XXXXXXXXXXXXX")
58
+ tracking.tracking_number
59
+ # => "XXXXXXXXXXXXX"
60
+
61
+ # Attribute accessors
62
+ tracking.tracking_number = "YYYYYYYYYYYYYYY"
63
+ tracking.tracking_number
64
+ # => "YYYYYYYYYYYYYYY"
65
+
66
+ # Mass attribute setting
67
+ tracking.attributes = {:tracking_number => "ZZZZZZZZZZZZZZZZ"}
68
+ tracking.tracking_number
69
+ # => "ZZZZZZZZZZZZZZZZ"
70
+
71
+ # Mass attribute reading
72
+ tracking.attributes
73
+ # => {:tracking_number => "ZZZZZZZZZZZZZZZZ"}
74
+
75
+ == Available services and their features
76
+
77
+ This library is still very new, as a result only FedEx is support at this time. More will come.
78
+
79
+ === FedEx
80
+
81
+ 1. <b>Tracking</b> - See Shippinglogic::Fedex::Track
82
+ 1. <b>Getting service rates</b> - See Shippinglogic::Fedex::Rates
83
+
84
+ === Add your own services
85
+
86
+ Simply fork the project and make your changes. If you want to add support for a new service it should be fairly straight forward. Checkout the code in Shippinglogic::Fedex::Track, it very simple and easy to follow. It's a great place to start because its the simplest of services.
87
+
88
+ == Interface integration
89
+
90
+ What's nice about having an object is that you can pass it around. Let's say you wanted to add simple FedEx tracking functionality to your app:
91
+
92
+ class TrackingController < ApplicationController
93
+ def new
94
+ @tracking = fedex.track(params[:tracking])
95
+ end
96
+
97
+ def create
98
+ @tracking = fedex.track(params[:tracking])
99
+ render :action => :new if !@tracking.successful?
100
+ end
101
+
102
+ private
103
+ def fedex
104
+ @fedex ||= Shippinglogic::FedEx.new(key, password, account, meter)
105
+ end
106
+ end
107
+
108
+ That's pretty simple. Now check out your form:
109
+
110
+ # new.html.haml
111
+ - form_for @tracking do |f|
112
+ = f.error_messages
113
+ = f.text_field :tracking_number
114
+ = f.submit "Track"
115
+
116
+ Then your results:
117
+
118
+ # create.html.haml
119
+ - @tracking.each do |event|
120
+ .event
121
+ .name= event.name
122
+ .occured_at= event.occured_at.to_s(:long)
123
+ .location== #{event.city}, #{event.state} #{event.postal_code}, #{event.country}
124
+ .residential= event.residential ? "Yes" : "No"
125
+
126
+ == Copyright
127
+
128
+ Copyright (c) 2009 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "shippinglogic"
8
+ gem.summary = "A simple and clean library to interface with shipping carriers"
9
+ gem.description = "A simple and clean library to interface with shipping carriers"
10
+ gem.email = "bjohnson@binarylogic.com"
11
+ gem.homepage = "http://github.com/binarylogic/shippinglogic"
12
+ gem.authors = ["Ben Johnson of Binary Logic"]
13
+ gem.rubyforge_project = "shippinglogic"
14
+ gem.add_dependency "activesupport", ">= 2.2.0"
15
+ gem.add_dependency "httparty", ">= 0.4.4"
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ begin
37
+ require 'rake/contrib/sshpublisher'
38
+ namespace :rubyforge do
39
+ desc "Release gem to RubyForge"
40
+ task :release => ["rubyforge:release:gem"]
41
+ end
42
+ rescue LoadError
43
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
44
+ end
45
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 9
@@ -0,0 +1,3 @@
1
+ require "httparty"
2
+ require "activesupport"
3
+ require "shippinglogic/fedex"
@@ -0,0 +1,64 @@
1
+ require "shippinglogic/fedex/error"
2
+ require "shippinglogic/fedex/service"
3
+ require "shippinglogic/fedex/rates"
4
+ require "shippinglogic/fedex/track"
5
+
6
+ module Shippinglogic
7
+ class FedEx
8
+ # A hash representing default the options. If you are using this in a Rails app the best place
9
+ # to modify or change these options is either in an initializer or your specific environment file. Keep
10
+ # in mind that these options can be modified on the instance level when creating an object. See #initialize
11
+ # for more details.
12
+ #
13
+ # === Options
14
+ #
15
+ # * <tt>:test</tt> - this basically tells us which url to use. If set to true we will use the FedEx test URL, if false we
16
+ # will use the production URL. If you are using this in a rails app, unless you are in your production environment, this
17
+ # will default to true automatically.
18
+ # * <tt>:test_url</tt> - the test URL for FedEx's webservices. (default: https://gatewaybeta.fedex.com:443/xml)
19
+ # * <tt>:production_url</tt> - the production URL for FedEx's webservices. (default: https://gateway.fedex.com:443/xml)
20
+ def self.options
21
+ @options ||= {
22
+ :test => defined?(Rails) && !Rails.env.production?,
23
+ :production_url => "https://gateway.fedex.com:443/xml",
24
+ :test_url => "https://gatewaybeta.fedex.com:443/xml"
25
+ }
26
+ end
27
+
28
+ attr_accessor :key, :password, :account, :meter, :options
29
+
30
+ # Before you can use the FedEx web services you need to provide 4 credentials:
31
+ #
32
+ # 1. Your fedex web service key
33
+ # 2. Your fedex password
34
+ # 3. Your fedex account number
35
+ # 4. Your fedex meter number
36
+ #
37
+ # You can easily get these things by logging into your fedex account and going to:
38
+ #
39
+ # https://www.fedex.com/wpor/wpor/editConsult.do
40
+ #
41
+ # If for some reason this link no longer works because FedEx changed it, just go to the
42
+ # developer resources area and then navigate to the FedEx web services for shipping area. Once
43
+ # there you should see a link to apply for a develop test key.
44
+ #
45
+ # The last parameter allows you to modify the class options on an instance level. It accepts the
46
+ # same options that the class level method #options accepts. If you don't want to change any of
47
+ # them, don't supply this parameter.
48
+ def initialize(key, password, account, meter, options = {})
49
+ self.key = key
50
+ self.password = password
51
+ self.account = account
52
+ self.meter = meter
53
+ self.options = self.class.options.merge(options)
54
+ end
55
+
56
+ def rates(attributes = {})
57
+ @rates ||= Rates.new(self, attributes)
58
+ end
59
+
60
+ def track(attributes = {})
61
+ @track ||= Track.new(self, attributes)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,118 @@
1
+ module Shippinglogic
2
+ class FedEx
3
+ # Adds in all of the reading / writing for the various serivce options.
4
+ module Attributes
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Define an attribute for a class, makes adding options / attributes to the class
14
+ # much cleaner. See the Rates class for an example.
15
+ def attribute(name, type, options = {})
16
+ name = name.to_sym
17
+ options[:type] = type.to_sym
18
+ attributes[name] = options
19
+
20
+ define_method(name) { read_attribute(name) }
21
+ define_method("#{name}=") { |value| write_attribute(name, value) }
22
+ end
23
+
24
+ # A hash of all the attributes and their options
25
+ def attributes
26
+ @attributes ||= {}
27
+ end
28
+
29
+ # An array of the attribute names
30
+ def attribute_names
31
+ attributes.keys
32
+ end
33
+
34
+ # Returns the options specified when defining a specific attribute
35
+ def attribute_options(name)
36
+ attributes[name.to_sym]
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ # A convenience so that you can set attributes while initializing an object
42
+ def initialize(base, attributes = {})
43
+ @attributes = {}
44
+ self.attributes = attributes
45
+ end
46
+
47
+ # Returns a hash of the various attribute values
48
+ def attributes
49
+ attributes = {}
50
+ attribute_names.each do |name|
51
+ attributes[name] = send(name)
52
+ end
53
+ attributes
54
+ end
55
+
56
+ # Accepts a hash of attribute values and sets each attribute to those values
57
+ def attributes=(value)
58
+ return if value.blank?
59
+ value.each do |key, value|
60
+ next if !attribute_names.include?(key.to_sym)
61
+ send("#{key}=", value)
62
+ end
63
+ end
64
+
65
+ private
66
+ def attribute_names
67
+ self.class.attribute_names
68
+ end
69
+
70
+ def attribute_options(name)
71
+ self.class.attribute_options(name)
72
+ end
73
+
74
+ def attribute_type(name)
75
+ attribute_options(name)[:type]
76
+ end
77
+
78
+ def attribute_default(name)
79
+ default = attribute_options(name)[:default]
80
+ case default
81
+ when Proc
82
+ default.call
83
+ else
84
+ default
85
+ end
86
+ end
87
+
88
+ def write_attribute(name, value)
89
+ @attributes[name.to_sym] = value
90
+ end
91
+
92
+ def read_attribute(name)
93
+ name = name.to_sym
94
+ value = @attributes[name].nil? ? attribute_default(name) : @attributes[name]
95
+ type = attribute_type(name)
96
+ return nil if value.nil? && type != :array
97
+
98
+ case type
99
+ when :array
100
+ value.is_a?(Array) ? value : [value].compact
101
+ when :integer
102
+ value.to_i
103
+ when :float
104
+ value.to_f
105
+ when :big_decimal
106
+ BigDecimal.new(value)
107
+ when :boolean
108
+ ["true", "1"].include?(value.to_s)
109
+ when :string, :text
110
+ value.to_s
111
+ else
112
+ value
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,47 @@
1
+ module Shippinglogic
2
+ class FedEx
3
+ # If FedEx responds with an error, we try our best to pull the pertinent information out of that
4
+ # response and raise it with this object. Any time FedEx says there is a problem an object of this
5
+ # class will be raised.
6
+ #
7
+ # === Tip
8
+ #
9
+ # If you want to see the raw respose catch the error object and call the response method. Ex:
10
+ #
11
+ # begin
12
+ # # my fedex code
13
+ # rescue Shippinglogic::FedEx::Error => e
14
+ # # do whatever you want here, just do:
15
+ # # e.response
16
+ # # to get the raw response from fedex
17
+ # end
18
+ class Error < StandardError
19
+ attr_accessor :code, :message, :response
20
+
21
+ def initialize(response)
22
+ self.response = response
23
+
24
+ if response.blank?
25
+ self.message = "The response from FedEx was blank."
26
+ elsif !response.is_a?(Hash)
27
+ self.message = "The response from FedEx was malformed and was not in a valid XML format."
28
+ elsif notifications = response[:notifications]
29
+ self.code = notifications[:code]
30
+ self.message = notifications[:message]
31
+ elsif response[:"soapenv:fault"] && detail = response[:"soapenv:fault"][:detail][:"con:fault"]
32
+ self.code = detail[:"con:error_code"]
33
+ self.message = detail[:"con:reason"]
34
+ else
35
+ self.message = "There was a problem with your fedex request, and we couldn't locate a specific error message. This means your response " +
36
+ "was in an unexpected format. You might try glancing at the raw response by using the 'response' method on this error object."
37
+ end
38
+
39
+ error_message = ""
40
+ error_message += "(Error: #{code}) " if code
41
+ error_message = message
42
+
43
+ super(error_message)
44
+ end
45
+ end
46
+ end
47
+ end