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

Expire Carts after 300 seconds #104 #105

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/cart_payments_controller.rb
Expand Up @@ -12,7 +12,7 @@ class CartPaymentsController < ApplicationController
param :path, :cart_id, :integer, :required, 'Cart ID'
param :body, :cart, :versionCart, :required, 'Cart'
response :ok, 'Success', :readTransaction
response :not_found, 'No cart with that ID'
response :not_found, 'No cart with that ID or it is expired'
response :conflict, 'The user\'s balance is too low'
response :precondition_required, 'The cart is stale'
response :gone, 'The cart payment is already being processed'
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/carts_controller.rb
Expand Up @@ -48,6 +48,9 @@ class CartsController < ApplicationController
property :id, :integer, :optional, 'Cart ID'
property :user_id, :integer, :optional, 'User ID'
property :total_price, :integer, :optional, 'Total Cart price'
property :expires_at, :date_time, :optional,
'Date and time at which the cart expires, making it read-only, '\
'not payable for, and releasing product reservations'
property :lock_version, :integer, :optional, 'Cart version'
property :cart_items, :array, :optional, 'Cart Items',
'items' => {'$ref' => 'readCartItem'}
Expand Down Expand Up @@ -76,7 +79,7 @@ class CartsController < ApplicationController
before_action -> { doorkeeper_authorize! :checkout }

def show
cart = Cart.find_by_id(params[:id])
cart = Cart.find_by(id: params[:id])

if cart.present?
render json: cart
Expand All @@ -103,6 +106,7 @@ def update
if cart.save
render json: cart, status: :ok, location: cart
else
cart.reload
render json: cart, status: :conflict, location: cart
end
end
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/products_controller.rb
Expand Up @@ -98,7 +98,7 @@ class ProductsController < ApplicationController
before_action -> { doorkeeper_authorize! :admin }, only: [:create, :update]

def show
product = Product.find_by_id(params[:id])
product = Product.find_by(id: params[:id])

if product.present?
render json: product
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/transactions_controller.rb
Expand Up @@ -69,7 +69,7 @@ class TransactionsController < ApplicationController
before_action :doorkeeper_authorize!

def show
transaction = Transaction.find_by_id(params[:id])
transaction = Transaction.find_by(id: params[:id])

if transaction.present?
renter json: transaction
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/users_controller.rb
Expand Up @@ -78,7 +78,7 @@ class UsersController < ApplicationController
before_action -> { doorkeeper_authorize! :admin }, only: [:create, :update]

def show
user = User.find_by_id(params[:id])
user = User.find_by(id: params[:id])

if user.present?
render json: user
Expand Down
7 changes: 7 additions & 0 deletions app/interactors/abort_if_expired_cart.rb
@@ -0,0 +1,7 @@
class AbortIfExpiredCart
include Interactor

def call
context.fail!(message: 'generic.not_found') if context.cart.expired?
end
end
1 change: 1 addition & 0 deletions app/interactors/pay_cart.rb
Expand Up @@ -2,6 +2,7 @@ class PayCart
include Interactor::Organizer

organize ResolveCartAndUser,
AbortIfExpiredCart,
MarkCartAsProcessing,
InitializeTransaction,
LogRequestingClientInTransaction,
Expand Down
2 changes: 1 addition & 1 deletion app/interactors/resolve_cart_and_user.rb
Expand Up @@ -3,7 +3,7 @@ class ResolveCartAndUser

def call
begin
context.cart = Cart.find_by_id!(context.cart_id)
context.cart = Cart.find_by!(id: context.cart_id)
rescue ActiveRecord::RecordNotFound
context.fail!(message: 'generic.not_found')
end
Expand Down
2 changes: 1 addition & 1 deletion app/interactors/resolve_user.rb
Expand Up @@ -2,7 +2,7 @@ class ResolveUser
include Interactor

def call
context.user = User.find_by_id!(context.user_id)
context.user = User.find_by!(id: context.user_id)
rescue ActiveRecord::RecordNotFound
context.fail!(message: 'generic.not_found')
end
Expand Down
26 changes: 26 additions & 0 deletions app/models/cart.rb
@@ -1,8 +1,34 @@
class Cart < ActiveRecord::Base
EXPIRE_AFTER = 300 # seconds

belongs_to :user
has_many :cart_items, dependent: :destroy, autosave: true

validate :ensure_unexpired

scope :unexpired, -> { where unexpired_condition }

def self.unexpired_condition
arel_table[:updated_at].gteq(EXPIRE_AFTER.seconds.ago)
end

def total_price
cart_items.map(&:total_price).reduce(:+)
end

def expires_at
(updated_at || DateTime.current) + EXPIRE_AFTER.seconds
end

def expired?
DateTime.current > expires_at
end

private

def ensure_unexpired
if expired?
errors.add :expires_at, "can't be in the past"
end
end
end
2 changes: 2 additions & 0 deletions app/models/cart_item.rb
Expand Up @@ -5,6 +5,8 @@ class CartItem < ActiveRecord::Base

validate :enough_items_present_in_pricing

scope :unexpired, -> { joins(:cart).where(Cart.unexpired_condition) }

def product_name
product.try(:name) || ''
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/pricing.rb
Expand Up @@ -6,6 +6,6 @@ class Pricing < ActiveRecord::Base
validates :price, numericality: { only_integer: true }

def available_quantity
quantity - cart_items.sum(:quantity)
quantity - cart_items.unexpired.sum(:quantity)
end
end
1 change: 1 addition & 0 deletions app/representers/cart_representer.rb
Expand Up @@ -4,6 +4,7 @@ class CartRepresenter < ApplicationDecorator
property :lock_version, type: Integer
property :user_id, type: Integer
property :total_price, writeable: false, type: Integer
property :expires_at, writeable: false, type: Date

collection(
:cart_items,
Expand Down
6 changes: 3 additions & 3 deletions spec/controllers/carts_controller_spec.rb
Expand Up @@ -11,7 +11,7 @@
}

describe '#show' do
before {Cart.find_by_id(1) || create(:cart, id: 1)}
before { Cart.find_by(id: 1) || create(:cart, id: 1) }
before { get :show, params: { id: cart_id } }

context 'when the resource exists' do
Expand Down Expand Up @@ -49,14 +49,14 @@

describe '#update via JSON' do
before do
cart = Cart.find_by_id(1) || create(:cart, id: 1)
cart = Cart.find_by(id: 1) || create(:cart, id: 1)
cart.update_attribute(:user_id, 2)
end
before {request.env['CONTENT_TYPE'] = 'application/json'}

describe 'with valid parameters' do
it 'updates the Cart 1' do
cart = Cart.find_by_id(1)
cart = Cart.find_by(id: 1)
expect {
put :update, body: JSON.generate(valid_attributes), params: { id: '1' }
cart.reload
Expand Down
10 changes: 6 additions & 4 deletions spec/controllers/products_controller_spec.rb
Expand Up @@ -11,7 +11,7 @@
}

describe '#show' do
before {Product.find_by_id(1) || create(:product, id: 1)}
before { Product.find_by(id: 1) || create(:product, id: 1) }
before { get :show, params: { id: product_id } }

context 'when the resource exists' do
Expand All @@ -32,7 +32,9 @@
before {get :index}

context 'when there are products' do
before {(1..3).each {|y| Product.find_by_id(y) || create(:product, id: y)}}
before do
(1..3).each { |y| Product.find_by(id: y) || create(:product, id: y) }
end

its(:content_type) {is_expected.to eq 'application/json'}
it {is_expected.to have_http_status(:ok)}
Expand Down Expand Up @@ -65,14 +67,14 @@

describe '#update via JSON' do
before do
product = Product.find_by_id(1) || create(:product, id: 1)
product = Product.find_by(id: 1) || create(:product, id: 1)
product.update_attribute(:name, 'Turbriskafil')
end
before { request.env['CONTENT_TYPE'] = 'application/json' }

describe 'with valid parameters' do
it 'updates the Product 1' do
product = Product.find_by_id(1)
product = Product.find_by(id: 1)
expect {
put :update, body: JSON.generate(valid_attributes), params: { id: '1' }
product.reload
Expand Down
6 changes: 3 additions & 3 deletions spec/controllers/users_controller_spec.rb
Expand Up @@ -11,7 +11,7 @@
}

describe '#show' do
before {User.find_by_id(1) || create(:user, id: 1)}
before { User.find_by(id: 1) || create(:user, id: 1) }
before { get :show, params: { id: user_id } }

context 'when the resource exists' do
Expand Down Expand Up @@ -49,14 +49,14 @@

describe '#update via JSON' do
before do
user = User.find_by_id(1) || create(:user, id: 1)
user = User.find_by(id: 1) || create(:user, id: 1)
user.update_attribute(:name, 'Sheldon')
end
before { request.env['CONTENT_TYPE'] = 'application/json' }

describe 'with valid parameters' do
it 'updates the User 1' do
user = User.find_by_id(1)
user = User.find_by(id: 1)
expect {
put :update, body: JSON.generate(valid_attributes), params: { id: '1' }
user.reload
Expand Down
9 changes: 5 additions & 4 deletions spec/interactors/pay_cart_spec.rb
Expand Up @@ -31,7 +31,7 @@
end

before do
allow(Cart).to receive(:find_by_id!).with(cart.id).and_return(cart)
allow(Cart).to receive(:find_by!).with(id: cart.id).and_return(cart)
end

context 'when everything is swell' do
Expand Down Expand Up @@ -108,7 +108,7 @@

context 'when there\'s no cart with that ID' do
before do
allow(Cart).to receive(:find_by_id!).with(cart.id).
allow(Cart).to receive(:find_by!).with(id: cart.id).
and_raise(ActiveRecord::RecordNotFound)
end

Expand Down Expand Up @@ -162,8 +162,9 @@

context 'when things come in fast', :transactional do
it 'shouldn\'t do things twice' do
# Always returning the same cart object shares it between threads
allow(Cart).to receive(:find_by_id!).with(cart.id).and_call_original
# Don't return the object in the cart variable, as this would share
# the same instance of the object between all threads
allow(Cart).to receive(:find_by!).with(id: cart.id).and_call_original
expect do
threaded(3) do
PayCart.new(
Expand Down
4 changes: 2 additions & 2 deletions spec/interactors/resolve_cart_and_user_spec.rb
Expand Up @@ -9,7 +9,7 @@
let(:cart) { double user: user }

before do
allow(Cart).to receive(:find_by_id!).with(1).and_return(cart)
allow(Cart).to receive(:find_by!).with(id: 1).and_return(cart)
end

context 'when the cart exists' do
Expand All @@ -30,7 +30,7 @@

context 'when the cart doesn\'t exist' do
before do
allow(Cart).to receive(:find_by_id!).with(1).
allow(Cart).to receive(:find_by!).with(id: 1).
and_raise(ActiveRecord::RecordNotFound)
end
before { interactor.call rescue Interactor::Failure }
Expand Down
4 changes: 2 additions & 2 deletions spec/interactors/resolve_user_spec.rb
Expand Up @@ -8,7 +8,7 @@

context 'when the user exists' do
before do
allow(User).to receive(:find_by_id!).with(1).and_return(user)
allow(User).to receive(:find_by!).with(id: 1).and_return(user)
end

before { interactor.call }
Expand All @@ -24,7 +24,7 @@

context 'when the user doesn\'t exist' do
before do
allow(User).to receive(:find_by_id!).
allow(User).to receive(:find_by!).
and_raise(ActiveRecord::RecordNotFound)
end

Expand Down
2 changes: 1 addition & 1 deletion spec/interactors/user_deposit_spec.rb
Expand Up @@ -34,7 +34,7 @@

context 'when there\'s no user with that ID' do
before do
allow(User).to receive(:find_by_id!).
allow(User).to receive(:find_by!).
and_raise(ActiveRecord::RecordNotFound)
end

Expand Down
5 changes: 4 additions & 1 deletion spec/models/pricing_spec.rb
Expand Up @@ -11,7 +11,10 @@
end

context 'there are currently blocked items' do
before {create_list(:cart_item, 2, pricing: pricing, quantity: 2)}
before do
cart = create(:cart)
create_list(:cart_item, 2, cart: cart, pricing: pricing, quantity: 2)
end
it {is_expected.to eq 6}
end
end
Expand Down
9 changes: 7 additions & 2 deletions spec/models/product_spec.rb
Expand Up @@ -28,8 +28,13 @@

context 'the product is in some carts' do
before do
create(:cart_item, pricing: product.pricings[0], quantity: 3)
create(:cart_item, pricing: product.pricings[1], quantity: 2)
cart = create(:cart)
create(
:cart_item, cart: cart, pricing: product.pricings[0], quantity: 3
)
create(
:cart_item, cart: cart, pricing: product.pricings[1], quantity: 2
)
end

it {is_expected.to eq 15}
Expand Down