Sometimes we need to be able to obtain shipping costs. An example would be for an ecommerce site. This article will show you how to get those costs from UPS, FedEx, and the United States Postal Service using ActiveShipping. In this article we will build a simple shipping calculator that takes input from the user and returns the shipping costs for various carriers.

First, lets install the active_shipping gem. Open up your gemfile and add the following line.
Gemfile:

gem 'active_shipping'

Now, lets run a bundle install.

Terminal Commands:

bundle install

Now, we need to create a model to store the shipment. This will allow us to keep track of previously created shipments for example purposes. In addition, lets creeate a controller as well using the rails g resource command.

Terminal Commands:

rails g resource Shipment name:string country:string city:string state:string postal_code:string length:float width:float height:float weight:float cylinder:boolean
rake db:migrate

Greate, now that we have our model and controller, lets add some code to the controller to process form input. Open up the shipments controller and add the following code.

app/controllers/shipments_controller.rb:

class ShipmentsController < ApplicationController
  def index
    @shipments = Shipment.all
  end

  def show
    @shipment = Shipment.find(params[:id])
  end

  def new
    @shipment = Shipment.new
  end

  def create
    @shipment = Shipment.new(shipment_params)
    if @shipment.save
      redirect_to @shipment, notice: "The shipment has been successfully created.  Below are your shipping options."
    else
      render action: "new"
    end
  end

  def edit
    @shipment = Shipment.find(params[:id])
  end

  def update
    @shipment = Shipment.find(params[:id])
    if @shipment.update_attributes(shipment_params)
      redirect_to @shipment, notice: "The shipment has been successfully updated.  Below are your shipping options."
    else
      render action: "edit"
    end
  end

private

  def shipment_params
    params.require(:shipment).permit(:name, :country, :city, :state, :postal_code, :length, :width, :height, :weight, :cylinder)
  end
end

Next, lets set the site root to our shipments controller. Add the following line to your routes.rb file.

config/routes.rb:

root to: "shipments#index"

Next, lets create our index view. Create the index view for shipments and add the following code.

app/views/shipments/index.html.erb:

<h3>Shipments</h3>
<%= link_to "New Shipment", new_shipment_path %>
<table style="width:100%; text-align:left;">
  <thead>
    <th>&nbsp;</th>
    <th>&nbsp;</th>
    <th>Name</th>
    <th>Country</th>
    <th>City</th>
    <th>State</th>
    <th>Postal Code</th>
    <th>Length</th>
    <th>Width</th>
    <th>Height</th>
    <th>Weight</th>
    <th>Cylinder?</th>
  </thead>

  <tbody>
    <% @shipments.each do |shipment| %>
      <tr>
        <td><%= link_to "View", shipment %></td>
        <td><%= link_to "Edit", edit_shipment_path(shipment) %></td>
        <td><%= shipment.name %></td>
        <td><%= shipment.country %></td>
        <td><%= shipment.city %></td>
        <td><%= shipment.state %></td>
        <td><%= shipment.postal_code %></td>
        <td><%= "#{shipment.length} centimeters" %></td>
        <td><%= "#{shipment.width} centimeters" %></td>
        <td><%= "#{shipment.height} centimeters" %></td>
        <td><%= "#{shipment.weight} grams" %></td>
        <td><%= shipment.cylinder ? "Yes" : "No" %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Now, lets create a partial for storing the form. Create a partial in app/views/shipments called _form.html.erb and add the following code.

app/views/shipments/_form.html.erb:

<div>
For length, width, and height, use centimeters.  For weight, use grams.
</div>

<% if @shipment.errors.any? %>
  <ul>
    <%- @shipment.errors.full_messages.each do |message| %>
        <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

<%= form_for @shipment do |f| %>
  <div><%= f.label :name  %></div>
  <div><%= f.text_field :name %></div>
  <div><%= f.label :country %></div>
  <div><%= f.text_field :country %></div>
  <div><%= f.label :city %></div>
  <div><%= f.text_field :city %></div>
  <div><%= f.label :state %></div>
  <div><%= f.text_field :state %></div>
  <div><%= f.label :postal_code %></div>
  <div><%= f.text_field :postal_code %></div>
  <div><%= f.label :length %></div>
  <div><%= f.text_field :length %></div>
  <div><%= f.label :width %></div>
  <div><%= f.text_field :width %></div>
  <div><%= f.label :height %></div>
  <div><%= f.text_field :height %></div>
  <div><%= f.label :weight %></div>
  <div><%= f.text_field :weight %></div>
  <div>
    <%= f.check_box :cylinder %>
    <%= f.label :cylinder %>
  </div>

  <div><%= f.submit %></div>
<% end %>

Great, now lets create the new view for shipments. Create a file in app/views/shipments called new.html.erb and add the following code.

app/views/shipments/new.html.erb

<h3>New Shipment</h3>
<%= render "form" %>

Next, lets add the code for the edit shipments view. Create a file in app/views/shipments called edit.html.erb and add the following code.

app/views/shipments/edit.html.erb

<h3><%= "Editing Shipment for #{@shipment.name}" %></h3>
<%= render "form" %>

Great, now you will need developer access to the shipping services. Where you get that access from depends on the service(s) you wish to use. UPS, FedEx, and USPS are listed below.

Great, now we need to add some code to our shipment model. Open the shipment model and modify it so it looks like the following code listing.

app/models/shipment.rb:

class Shipment < ActiveRecord::Base
  include ActiveMerchant::Shipping

  validates :name, presence: true
  validates :country, presence: true
  validates :city, presence: true
  validates :state, presence: true
  validates :postal_code, presence: true
  validates :length, presence: true
  validates :width, presence: true
  validates :height, presence: true
  validates :weight, presence: true
  

  def origin
    Location.new(country: "US", state: "CA", city: "Los Angeles", postal_code: "90001")
  end

  def destination
    Location.new(country: country, state: state, city: city, postal_code: postal_code)
  end

  def packages
    package = Package.new(weight, [length, width, height], cylinder: cylinder)
  end

  def get_rates_from_shipper(shipper)
    response = shipper.find_rates(origin, destination, packages)
    response.rates.sort_by(&:price)
  end

  def ups_rates
    ups = UPS.new(login: 'your ups login', password: 'your ups password', key: 'your ups xml key')
    get_rates_from_shipper(ups)
  end

  def fedex_rates
    fedex = FedEx.new(login: "your fedex login", password: "your fedex password", key: "your fedex key", account: "your fedex account number")
    get_rates_from_shipper(fedex)
  end

  def usps_rates
    usps = USPS.new(login: 'your usps account number', password: 'your usps password')
    get_rates_from_shipper(usps)
  end
end

Great! Make sure you replace information in origin with your own country, state, etc. In addition, make sure you fill in your own logins, account keys, etc. for each of the 3 shippers. In a production system you'd probably want to store this information in session variables or a YAML file, but for this example it's ok to hard code things (just don't push it to github.) Make sure to use your 2 digit country code and 2 digit state code for the country and state.

Next we need to create the show view for the Shipment model. This file will actually display shipping options along with the shipping information. Add the code listed below.

app/views/shipments/show.html.erb:

<%= link_to "Edit shipment", edit_shipment_path(@shipment) %>&nbsp;|&nbsp;
<%= link_to "Back to Index", shipments_path %>

<h3>Shipping Information</h3>
<table style="width:100%; text-align:left;">
  <tr>
    <th>Name</th>
    <th>Country</th>
    <th>City</th>
    <th>State</th>
    <th>Zip</th>
  </tr>
  <tr>
    <td><%= @shipment.name %></td>
    <td><%= @shipment.country %></td>
    <td><%= @shipment.city %></td>
    <td><%= @shipment.state %></td>
    <td><%= @shipment.postal_code %></td>
  </tr>
  <tr>
    <th>Length</th>
    <th>Width</th>
    <th>Height</th>
    <th>Weight</th>
    <th>Cylinder?</th>
  </tr>
  <tr>
    <td><%= "#{@shipment.length} centimeters" %></td>
    <td><%= "#{@shipment.width} centimeters" %></td>
    <td><%= "#{@shipment.height} centimeters" %></td>
    <td><%= "#{@shipment.weight} grams" %></td>
    <td><%= @shipment.cylinder ? "Yes" : "No" %></td>
  </tr>
</table>

<h3>Shipping Options</h3>
<table>
  <tr>
    <th>Service Name</th>
    <th>Price</th>
  </tr>
  <% @shipment.ups_rates.each do |rate| %>
    <tr>
      <td><%= rate.service_name %></td>
      <td><%= number_to_currency(rate.price.to_f / 100) %></td>
    </tr>
  <% end %>
  <% @shipment.fedex_rates.each do |rate| %>
    <tr>
      <td><%= rate.service_name %></td>
      <td><%= number_to_currency(rate.price.to_f / 100) %></td>
    </tr>
  <% end %>
  <% @shipment.usps_rates.each do |rate| %>
    <tr>
      <td><%= rate.service_name %></td>
      <td><%= number_to_currency(rate.price.to_f / 100) %></td>
    </tr>
  <% end %>
</table>

Excellent! Now if you start and visit your development server you will see a link at the top to create a new shipment. Click this link and fill out the information. Make sure to use your 2 digit country code and 2 digit state code. For example, US for united states and CA for california. Fill out the information and click create shipment. If all goes well you'll be presented with your shipment info and a list of shipping options.

Great! You've now built a basic shipping calculator that uses ActveShipping to get the shipping rates. For more information on ActiveShipping be sure to visit their github page at https://github.com/Shopify/active_shipping. Thanks for reading!