Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shipping-service Pull Request #21

Open
wants to merge 80 commits into
base: alddfp/master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
5712765
Adding estimators controller and prelim junk code
Dreedle Jan 20, 2016
7814240
Making QuoteCalculator class in Estimator module
Dreedle Jan 20, 2016
7297a29
Removing ups method from estimates controller
Dreedle Jan 20, 2016
f06d330
Stubbing method for querying quotecalculator
Dreedle Jan 20, 2016
7ca8a0b
Stubbing methods for generating a quote and getting shipping params f…
Dreedle Jan 20, 2016
721fc60
Stubbing params requirements
Dreedle Jan 20, 2016
cbcfb39
Adding very rough draft code to estimator module
Dreedle Jan 20, 2016
4d3f191
Comments in estimator about what to do about packages
Dreedle Jan 20, 2016
9be574e
add location factories
dezshino Jan 20, 2016
b36d2b8
remove pending spec
dezshino Jan 20, 2016
f854408
Add factories
dezshino Jan 20, 2016
626505a
Fix errors for code to run
dezshino Jan 20, 2016
16de70b
Fill out shipping params
dezshino Jan 20, 2016
c820490
Create specs for Estimates#quote
dezshino Jan 20, 2016
23e43db
Working in estimator wrapper
Dreedle Jan 20, 2016
4b3818c
Using written params instead of factory for tests
Dreedle Jan 21, 2016
14045de
Got params hash to at least run in estimates controller
Dreedle Jan 21, 2016
0028dba
Trying to make estimator module available
Dreedle Jan 21, 2016
53d9c39
A bit more work on quote method
Dreedle Jan 21, 2016
03f7951
Adding pry to gemfile
Dreedle Jan 21, 2016
8c02322
Reached stopping point in testing success of quote method. Need to wr…
Dreedle Jan 21, 2016
9d064c9
Editing params hash in rspec
Dreedle Jan 21, 2016
f74c6ae
Making packages an array in params
Dreedle Jan 21, 2016
ad77239
Strong params for shipping seem to be formatted properly
Dreedle Jan 21, 2016
888e0c4
Fixing params variable name in estimates controller spec
Dreedle Jan 21, 2016
c66e105
Need to add json example
Dreedle Jan 21, 2016
71e98e9
Solved accidental resursive call
Dreedle Jan 21, 2016
3a802cd
First two tests passing- returning json successfully
Dreedle Jan 21, 2016
aa73735
Changed name of shipment to avoid name confusion with active shipping
Dreedle Jan 21, 2016
cc7f6fb
Removing binding
Dreedle Jan 21, 2016
567ed93
Fixed strong params to accept more than one package_item per package
Dreedle Jan 21, 2016
58ce33c
Removing binding
Dreedle Jan 21, 2016
df899ef
Successfully able to create an active ship location
Dreedle Jan 21, 2016
710efcb
Working on packing method to pack items from same merchant. Finished …
Dreedle Jan 21, 2016
0d7743b
Wrote methods for determining dimensions and weight for items package…
Dreedle Jan 21, 2016
3412d16
Can successfully make shipment_array of active shipping objects for a…
Dreedle Jan 21, 2016
6437ae5
UPS response received without error (questionable format though...)
Dreedle Jan 21, 2016
da3aa98
remove binding
Dreedle Jan 21, 2016
971d5ef
Able to get total ups rates for each package in the order
Dreedle Jan 21, 2016
8ddefb2
Had to add a package value or date estimate would not work
Dreedle Jan 22, 2016
fdce6ed
Can get est delivery dates from ups, but am not comparing them to eac…
Dreedle Jan 22, 2016
d11ae44
Removing binding
Dreedle Jan 22, 2016
582298c
Can return the most conservative date estimates from ups
Dreedle Jan 22, 2016
81fd487
MAY be returning json for ups requests
Dreedle Jan 22, 2016
d472e7c
Adding specfile for estimator.rb
Dreedle Jan 22, 2016
73e4224
Separated params hash to a support file
Dreedle Jan 22, 2016
64c2097
Added some error handling to estimator. Positive and negative tests f…
Dreedle Jan 22, 2016
1dfb001
Removing some unnecessary comments
Dreedle Jan 22, 2016
77b09a9
Took out ups date estimate methods :(
Dreedle Jan 22, 2016
863a50d
Passing specs with usps added
Dreedle Jan 22, 2016
ea10b5d
Changing route to a get instead of a post
Dreedle Jan 22, 2016
98d13ef
Working on testing json response
Dreedle Jan 22, 2016
e8f9d14
Wrote spec to test parsed response
Dreedle Jan 22, 2016
dfe9474
Adding one more test for parsed response
Dreedle Jan 22, 2016
82db458
Changing gems for heroku deployment
Dreedle Jan 22, 2016
0efd4cc
Modified gemfile.lock
Dreedle Jan 22, 2016
d86cd13
Adding curl to development
Dreedle Jan 22, 2016
bc601e5
Controller tweaked
Dreedle Jan 22, 2016
4e9256a
Remove binding
Dreedle Jan 22, 2016
adc0250
Requiring lib file in controller
Dreedle Jan 22, 2016
33283e5
Changing required filename
Dreedle Jan 22, 2016
296a9ce
heroku file issue
Dreedle Jan 22, 2016
ea3ad6b
Removed requiring pry and curl
Dreedle Jan 22, 2016
032ecf0
Taking out include estimator module
Dreedle Jan 22, 2016
2e2e4a8
Changed require file
Dreedle Jan 22, 2016
d53799d
File name in require
Dreedle Jan 22, 2016
6d1369d
Playing with require
Dreedle Jan 22, 2016
34c4809
Fixed require error
Dreedle Jan 22, 2016
dfae682
require
Dreedle Jan 22, 2016
f8058fb
test
Dreedle Jan 22, 2016
2e6d386
test2
Dreedle Jan 22, 2016
609e13b
test
Dreedle Jan 22, 2016
c759219
putting estimator.rb in a directory
Dreedle Jan 22, 2016
1fc1ff7
test
Dreedle Jan 22, 2016
6969c4c
test
Dreedle Jan 22, 2016
5e1bd60
Adding figaro gem
Dreedle Jan 22, 2016
d9ad28c
Adding figaro files
Dreedle Jan 22, 2016
e5d485d
config secrets
Dreedle Jan 22, 2016
211018f
Updating readme
Dreedle Jan 22, 2016
4b45806
Readme edit
Dreedle Jan 23, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/test/tmp/
/test/version_tmp/
/tmp/
/.env

## Specific to RubyMotion:
.dat*
Expand Down Expand Up @@ -71,3 +72,6 @@ bower.json

# Ignore pow environment settings
.powenv

# Ignore application configuration
/config/application.yml
9 changes: 7 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ gem 'active_shipping'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.5'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
Expand All @@ -22,7 +21,7 @@ gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc

gem "figaro"
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

Expand All @@ -31,15 +30,21 @@ gem 'sdoc', '~> 0.4.0', group: :doc

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
group :production do
gem 'pg'
end

group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
gem 'simplecov', require: false
gem 'factory_girl_rails', '~> 4.0'
gem 'pry'
gem 'curl'
end

group :development do
gem 'sqlite3'
# Access an IRB console on exception pages or by using <%= console %> in views
gem 'web-console', '~> 2.0'

Expand Down
18 changes: 18 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arel (6.0.3)
awesome_print (1.6.1)
better_errors (2.1.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
Expand All @@ -62,6 +63,9 @@ GEM
execjs
coffee-script-source (1.10.0)
concurrent-ruby (1.0.0)
curl (0.0.9)
awesome_print (>= 0.2.1)
unidecoder (>= 1.1.1)
debug_inspector (0.0.2)
diff-lcs (1.2.5)
docile (1.1.5)
Expand All @@ -72,6 +76,8 @@ GEM
factory_girl_rails (4.5.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
figaro (1.1.1)
thor (~> 0.14)
globalid (0.3.6)
activesupport (>= 4.1.0)
i18n (0.7.0)
Expand All @@ -87,12 +93,18 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.3)
mime-types (>= 1.16, < 3)
method_source (0.8.2)
mime-types (2.99)
mini_portile2 (2.0.0)
minitest (5.8.3)
multi_json (1.11.2)
nokogiri (1.6.7.1)
mini_portile2 (~> 2.0.0.rc2)
pg (0.18.4)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
quantified (1.0.1)
rack (1.6.4)
rack-test (0.6.3)
Expand Down Expand Up @@ -156,6 +168,7 @@ GEM
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (3.6.0)
spring (1.6.2)
sprockets (3.5.2)
concurrent-ruby (~> 1.0)
Expand All @@ -175,6 +188,7 @@ GEM
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
unidecoder (1.1.2)
web-console (2.2.1)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
Expand All @@ -190,9 +204,13 @@ DEPENDENCIES
binding_of_caller
byebug
coffee-rails (~> 4.1.0)
curl
factory_girl_rails (~> 4.0)
figaro
jbuilder (~> 2.0)
jquery-rails
pg
pry
rails (= 4.2.5)
rspec-rails (~> 3.0)
sass-rails (~> 5.0)
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
# Shipping Service API
# Shipping Service API by Desiree and Audrey

see progress here:
https://trello.com/b/TXJ6uwTM/shippingservice

This API was designed for somewhat general use, but our assigned marketplace was Wetsy. (http://wetsy.herokuapp.com/)
The fork of the wetsy repo we worked on is here: https://github.com/mangolatte/betsy/tree/alddfp/master`

Our project was a little cut short by the shortened week, so we did not accomplish all we had wanted to.

#Accomplished:#
-98.4% test coverage
-On localhost with two ports, able to test using API with wetsy, get shipping estimates, and add shipping cost to the order total.
-In an order from wetsy, items from different merchants are packaged together using a packing method in this api. That way, when you order multiple items from the same merchant they are packaged together, and these packages are sent from various origins to one destination.
-Some error handling for when Active shipping would not return shipping info (because of problems with the zip codes, etc.)

#Did not accomplish:#
-In retrospect, wish we had added destination to EACH package instead of having one destination. Having one destination was great for wetsy, but the API would be more generalized if it allowed for origin and destination for each package.
- Usernames/passwords for ups and usps are hardcoded, which is ok because they're already publicly on github but not ideal as far as best practices.
-Test coverage is high, but tests themselves are not thorough.
-Deploy shipping service on heroku (left off trying to get secrets to work properly)




Build a stand-alone shipping service API that calculates estimated shipping cost for an order from another team's bEtsy application.

## Learning Goals
Expand Down
3 changes: 3 additions & 0 deletions app/assets/javascripts/estimates.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/estimates.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the estimates controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
23 changes: 23 additions & 0 deletions app/controllers/estimates_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

class EstimatesController < ApplicationController
require 'active_shipping'
require "./lib/estimator/estimator.rb"

def quote
ship_params = strong_shipping_params
estimate = Estimator::Estimate.query(ship_params)
if estimate
render :json => estimate.to_json, :status => :ok
else
render :json => [], :status => :no_content
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ❤️ ❤️ ❤️ this quote method. It's only doing one thing and is super easy to follow.


private

def strong_shipping_params
params.require(:shipping_params).permit(:destination => [:country, :state, :city, :postal_code], :packages => {:origin => [:country, :state, :city, :postal_code], :package_items => [:weight, :height, :length, :width]})
end


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to clean up whitespace!

end
2 changes: 2 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
module ApplicationHelper


end
2 changes: 2 additions & 0 deletions app/helpers/estimates_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module EstimatesHelper
end
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

module ShippingService
class Application < Rails::Application
config.autoload_paths += %W(#{config.root}/lib/)
Rails.application.config.secret_key_base = ENV["SECRET_KEY_BASE"]
Rails.application.config.secret_token = ENV["SECRET_TOKEN"]
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
Expand Down
2 changes: 1 addition & 1 deletion config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# config.assets.css_compressor = :sass

# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
config.assets.compile = true

# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
Expand Down
15 changes: 1 addition & 14 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
Rails.application.routes.draw do
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".

# You can have the root of your site routed with "root"
# root 'welcome#index'

# Example of regular route:
# get 'products/:id' => 'catalog#view'

# Example of named route that can be invoked with purchase_url(id: product.id)
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase

# Example resource route (maps HTTP verbs to controller actions automatically):
# resources :products
get '/quote' => 'estimates#quote'

# Example resource route with options:
# resources :products do
Expand Down
16 changes: 16 additions & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 0) do

end
89 changes: 89 additions & 0 deletions lib/estimator/estimator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module Estimator
class Estimate < ActiveRecord::Base
require 'active_shipping'

def self.query(ship_params)
@api_call_ok = true
ship_array = shipment_array(ship_params)
ups_rates = get_rates(ship_array, ups)
usps_rates = get_rates(ship_array, usps)
quote = assemble_quote(ups_rates, usps_rates)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I like how you guys consolidated shipping estimates into a single method.

if @api_call_ok
return quote
else
return nil
end
@api_call_ok = true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stick me if I missed something, but this seems a little redundant. Since you don't change the value of instance variable anywhere in the method, you shouldn't need to reassign it's value to true again.

end

def self.ups
ActiveShipping::UPS.new(login: 'shopifolk', password: 'Shopify_rocks', key: '7CE85DED4C9D07AB')
end

def self.usps
ActiveShipping::USPS.new(login: '677JADED7283')
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good practice to start creating .env files to store login info and keys to make API calls. Even for fake ones! Make a habit out of it!

def self.shipment_array(ship_params)
#from params, goes through packages and gets each one in the format needed to make the call to active shipping
#the shipment array is an array of hashes. each hash has a key and the value is an object that can be used by active shipping to make the call to the shipping service
#notice that because there are multiple package items, each package needs to be packed by calling on the pack_items method
shipment_array = []
ship_params[:packages].each do |package|
package_info = pack_items(package)
active_origin = ActiveShipping::Location.new(package[:origin])
active_destination = destination(ship_params)
active_package = ActiveShipping::Package.new(package_info[:weight], package_info[:dimensions], :units => :imperial, :value => 10) #had to add value for delivery date estimate

package_hash = {origin: active_origin,
destination: active_destination,
package: active_package
}
shipment_array << package_hash
end
return shipment_array
end

def self.pack_items(package)
#different items from the same merchant will be packed together into one package
#a simple way to do this is to find the longest length and and add together the widths and heights. return the total weight and the dimensions of this package.
#this method assumes the length is the longest dimension
weight = package[:package_items].map {|item| item[:weight].to_i}.sum

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely appreciate the use of comments as a way to list assumptions going into the way the method is written. Thumbs up!

longest_package = package[:package_items].max_by{|item| item[:length]}
length = longest_package[:length].to_i
width = package[:package_items].map {|item| item[:width].to_i}.sum
height = package[:package_items].map {|item| item[:height].to_i}.sum
package_info = {weight: weight, dimensions: [length, width, height]}
return package_info
end

def self.destination(ship_params)
# keeping destination as a separate method since that's how params will come in from wetsy.
#would have been better maybe to keep it more general for our api, so that packages can have any destination. But that's ok.
ActiveShipping::Location.new(ship_params[:destination])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ship_params[:destination] is the only piece of ship_params you use in this method, just pass that directly to this method instead of including all of ship_params.

so it would look like:

def self.destination(destination)
  ActiveShipping::Locaiton.new(destination)
end

and when you call it:

Estimator::Estimate.destination(ship_params[:destination])

end

def self.get_rates(shipment_array, carrier)
#rates is an array of hashes with different shipping services and their cost.
#total_carrier_rates creates as array summing the cost of each of the packages for the same service
#this is where are are taking all the packages from each different merchant and summing their shipping costs
#error handling takes care of things when the zip code is invalid, etc
carrier_rates = []
shipment_array.each do |shipment|
response = carrier.find_rates(shipment[:origin], shipment[:destination], shipment[:package])
sorted_rates = (response.rates.sort_by(&:price).collect {|rate| [rate.service_name, rate.price]}).to_h
carrier_rates << sorted_rates
end
rescue ActiveShipping::ResponseError
@api_call_ok = false
else
total_carrier_rates = Hash.new(0)
carrier_rates.each { |subhash| subhash.each { |service, cost| total_carrier_rates[service] += cost } }
return total_carrier_rates
end

def self.assemble_quote(ups, usps)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name these parameters ups_rates and usps_rates like you do when you call the method up on line 10, just because it's a bit confusing inside the method when you reference ups and usps whether you are referring to these passed in parameters, or the class methods you have defined on lines 19 and 23.

{"UPS" => ups, "USPS" => usps}
end
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is doing A LOT. I would suggest separating the functionality out into multiple classes, specifically one class to act as a client file (to be solely responsible for connecting to the Active Shipping API) and a separate class to transform the data you get back from the Active Shipping API to a json format that your service will return to the client. This helps organize your thought process and makes it much easier to add on functionality when you get to that point in a project.

37 changes: 37 additions & 0 deletions spec/controllers/estimates_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'rails_helper'
require 'pry'
require 'curl'
require 'support/params_hash.rb'

RSpec.describe EstimatesController, type: :controller do
describe "POST 'quote'" do
include_context "using shipping params"

context "successful api call" do
it "is successful" do
get :quote, params, { format: :json }
expect(response.response_code).to eq 200
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be more precise, your "it" statement should be "returns a http status code of 200".


it "returns json" do
get :quote, params, { format: :json }
expect(response.header['Content-Type']).to include 'application/json'
end

it "returned json that is formatted properly" do
get :quote, params, { format: :json }
parsed_body = JSON.parse(response.body)
expect(parsed_body["UPS"]).to_not be_nil
expect(parsed_body["UPS"]["UPS Ground"]).to_not be_nil
expect(parsed_body["USPS"]).to_not be_nil
end
end

context "failed api call" do
it "returns a 204 (no content)" do
get :quote, bad_params, { format: :json }
expect(response.response_code).to eq 204
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why is it a failed API call? Which part has no content? Is the no content causing the API call to fail? Make your tests more precise so you know exactly what cases you're testing for.

end
end
end
1 change: 1 addition & 0 deletions spec/factories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading