Skip to content

Instantly share code, notes, and snippets.

@Nitesh9952
Last active April 4, 2020 19:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nitesh9952/90114f61c6ff71d8c6d9f67dacc18822 to your computer and use it in GitHub Desktop.
Save Nitesh9952/90114f61c6ff71d8c6d9f67dacc18822 to your computer and use it in GitHub Desktop.
Code Refactoring | Rails

Refactoring code is an important part of software development and there are many opportunities for refactoring in the Reflektive codebase. In this challenge, you will be presented with a complicated controller action that could benefit from refactoring.

The application is a typical e-commerce app. The controller action you will be refactoring is the order creation endpoint OrdersController#create.

Data model overview

For our application, we have Products with a price attribute. We have shopping Carts that have many Products through the OrderedItems join table. An OrderedItem belongs_to a Cart and a Product. It has a quantity attribute to keep track of the number of products ordered.

The OrderedItem also belongs_to an Order. This association will be made upon checkout when it's associated with an Order.

When a user checks out, the items in the Cart are associated with a new Order. The Order has a total price attribute that is calculated by adding up the number of OrderedItems' quantity multiplied by their price, plus tax and shipping.

The Controller Action

In our OrdersController#create endpoint, we do many things:

  1. Instantiate a new Order object.
  2. Add the items from the Cart record to the Order instance.
  3. Add shipping and tax to the total price of the Order instance.
  4. Process the user's credit card
  • instantiate an ActiveMerchant client
  • via ActiveMerchant check whether the credit card information is valid
    • if invalid, we stop the transaction and display an error to the user
  1. If the credit card is valid, we charge the card via ActiveMerchant
    • if charge fails, we stop the transaction and display an error to the user
  2. Set the Order's status attribute to processed and save the Order
module AddressHelper
def billing_address params
{ name: "#{params[:billing_first_name]} #{params[:billing_last_name]}",
address1: params[:billing_address_line_1],
city: params[:billing_city], state: params[:billing_state],
country: 'US',zip: params[:billing_zip],
phone: params[:billing_phone] }
end
end
#Some initializer file
# Process credit card
# Create a connection to ActiveMerchant
GATEWAY = ActiveMerchant::Billing::AuthorizeNetGateway.new(
login: ENV["AUTHORIZE_LOGIN"],
password: ENV["AUTHORIZE_PASSWORD"]
)
class Order < ActiveRecord::Base
attr_attributes :total
attr_accerr :billing_email
after_save :post_order_activities
def initialize params
self.billing_email = params[:billing_email]
# Add shipping and tax to order total
case params[:order][:shipping_method]
when 'ground'
self.total = (self.taxed_total).round(2)
when 'two-day'
self.total = self.taxed_total + (15.75).round(2)
when "overnight"
self.total = self.taxed_total + (25).round(2)
end
end
def change_amount
(self.total.to_f * 100).to_i
end
def processed
self.order_status = 'processed'
end
def get_items_from_cart cart
# Add items from cart to order's ordered_items association
cart.ordered_items.each do |item|
self.ordered_items << item
end
end
private
def post_order_activities
Cart.destroy(session[:cart_id])
# send order confirmation email
OrderMailer.order_confirmation(self.billing_email, session[:order_id]).deliver
return true
end
end
class OrdersController < ApplicationController
include PaymentHelper
before_action :get_cart
# process order
def create
@order = Order.new(order_params)
@order.get_items_from_cart @cart
payment_status, error_message = PaymentHelper::receive_payment(params)
if payment_status
@order.processed
if @order.save
flash[:success] = "You successfully ordered!"
redirect_to confirmation_orders_path
else
@order.errors.add(:error, error_message)
flash[:error] = "There was a problem processing your order. Please try again."
render :new
end
else
@order.errors.add(:error, error_message)
flash[:error] = "There was a problem processing your order. Please try again."
render :new
end
end
private
def order_params
params.require(:order).permit!
end
def get_cart
@cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
end
end
class OrdersController < ApplicationController
before_action :get_cart
# process order
def create
@order = Order.new(order_params)
# Add items from cart to order's ordered_items association
@cart.ordered_items.each do |item|
@order.ordered_items << item
end
# Add shipping and tax to order total
case params[:order][:shipping_method]
when 'ground'
@order.total = (@order.taxed_total).round(2)
when 'two-day'
@order.total = @order.taxed_total + (15.75).round(2)
when "overnight"
@order.total = @order.taxed_total + (25).round(2)
end
# Process credit card
# Create a connection to ActiveMerchant
gateway = ActiveMerchant::Billing::AuthorizeNetGateway.new(
login: ENV["AUTHORIZE_LOGIN"],
password: ENV["AUTHORIZE_PASSWORD"]
)
# Get the card type
card_type = get_card_type
# Get credit card object from ActiveMerchant
credit_card = ActiveMerchant::Billing::CreditCard.new(
number: params[:card_info][:card_number],
month: params[:card_info][:card_expiration_month],
year: params[:card_info][:card_expiration_year],
verification_value: params[:card_info][:cvv],
first_name: params[:card_info][:card_first_name],
last_name: params[:card_info][:card_last_name],
type: card_type
)
# Check if card is valid
if credit_card.valid?
billing_address = { name: "#{params[:billing_first_name]} #{params[:billing_last_name]}",
address1: params[:billing_address_line_1],
city: params[:billing_city], state: params[:billing_state],
country: 'US',zip: params[:billing_zip],
phone: params[:billing_phone] }
options = { address: {}, billing_address: billing_address }
# Make the purchase through ActiveMerchant
charge_amount = (@order.total.to_f * 100).to_i
response = gateway.purchase(charge_amount, credit_card, options)
if !response.success?
@order.errors.add(:error, "We couldn't process your credit card")
end
else
@order.errors.add(:error, "Your credit card seems to be invalid")
flash[:error] = "There was a problem processing your order. Please try again."
render :new && return
end
@order.order_status = 'processed'
if @order.save
# get rid of cart
Cart.destroy(session[:cart_id])
# send order confirmation email
OrderMailer.order_confirmation(order_params[:billing_email], session[:order_id]).deliver
flash[:success] = "You successfully ordered!"
redirect_to confirmation_orders_path
else
flash[:error] = "There was a problem processing your order. Please try again."
render :new
end
end
def order_params
params.require(:order).permit!
end
def get_card_type
length = params[:card_info][:card_number].size
if length == 15 && number =~ /^(34|37)/
"AMEX"
elsif length == 16 && number =~ /^6011/
"Discover"
elsif length == 16 && number =~ /^5[1-5]/
"MasterCard"
elsif (length == 13 || length == 16) && number =~ /^4/
"Visa"
else
"Unknown"
end
end
def get_cart
@cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
end
end
module PaymentHelper
include AddressHelper
def receive_payment params
payment_status = true
error_message = ""
# Get credit card object from ActiveMerchant
credit_card = ActiveMerchant::Billing::CreditCard.new(card_params(params))
# Check if card is valid
if credit_card.valid?
options = { address: {}, billing_address: billing_address(params) }
# Make the purchase through ActiveMerchant
response = GATEWAY.purchase(@order.charge_amount, credit_card, options)
if !response.success?
payment_status = false
error_message = "We couldn't process your credit card";
end
else
payment_status = false
error_message = "Your credit card seems to be invalid"
end
[payment_status, error_message]
end
def card_params params
card_type = get_card_type(params[:card_info][:card_number].size)
{ number: params[:card_info][:card_number],
month: params[:card_info][:card_expiration_month],
year: params[:card_info][:card_expiration_year],
verification_value: params[:card_info][:cvv],
first_name: params[:card_info][:card_first_name],
last_name: params[:card_info][:card_last_name],
type: card_type
}
end
def get_card_type length
if length == 15 && number =~ /^(34|37)/
"AMEX"
elsif length == 16 && number =~ /^6011/
"Discover"
elsif length == 16 && number =~ /^5[1-5]/
"MasterCard"
elsif (length == 13 || length == 16) && number =~ /^4/
"Visa"
else
"Unknown"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment