Skip to content

Instantly share code, notes, and snippets.

@rossnelson
Last active March 22, 2016 13:14
Show Gist options
  • Save rossnelson/35e2db73d89f956accd7 to your computer and use it in GitHub Desktop.
Save rossnelson/35e2db73d89f956accd7 to your computer and use it in GitHub Desktop.
Coupon Validator
class Store::CouponValidator < SimpleDelegator
include ActiveModel::Validations
include QueryUtils
include Store::ValidationUser
include Store::ValidationCoupon
attr_accessor :user_id, :coupon_code
def initialize(options)
@user_id = options[:user_id]
@coupon_code = options[:code]
super(user)
end
validators = [
:has_coupon?, :expired?, :active?, :active_for_price_level?,
:has_been_used?, :meets_cart_minimum?, :requires_new_customer?
]
validators.each do |meth|
validate meth, unless: :has_errors?
end
def has_errors?
errors.any?
end
def has_coupon?
return true if coupon
errors.add(:base, "coupon code (#{coupon_code} invalid.")
end
def expired?
return false if DateTime.now < coupon.end_date
errors.add(:base, "expired on #{coupon.end_date.strftime("%d/%m/%Y")}")
end
def active?
return true if DateTime.now > coupon.start_date
errors.add(:base, "not active until #{coupon.start_date.strftime("%d/%m/%Y")}")
end
def active_for_price_level?
return true if user.price_level_id == coupon.price_level_id
errors.add(:base, "not active for current customer type")
end
def has_been_used?
return handle_user_single_use if coupon.single_use == 'user'
return handle_store_single_use if coupon.single_use == 'store'
handle_already_in_cart
end
def meets_cart_minimum?
applicable_skus_total = cart_data.inject(0) { |sum, i| Monetize.parse(i['total']) + sum }
return true if applicable_skus_total > coupon.cart_min
errors.add(:base, "minimum not met - need $#{coupon.cart_min} in cart (currently at $#{applicable_skus_total})")
end
def requires_new_customer?
return true if user.plucked_order_ids.blank?
errors.add(:base, "can only be used by new customers")
end
def is_it_stackable?
return if current_coupons_in_cart.blank?
return handle_stackable if coupon.stackable
errors.add(:base, "#{coupon_code} removed - not stackable with other coupons")
end
def self.find_by_user_and_code(user, code)
self.new({user: user, code: code})
end
protected
def applicable_skus
@_aps = cart_data.select { |i| coupon.allowed_skus.include? i['sku'] }
end
def current_coupons_in_cart
@_ccic = cart_data.select { |i| i['parent_type'] == 'coupon' }
end
def handle_stackable
cart_has_unstackable = current_coupons_in_cart.any? { |i| i['parent']['stackable'] == false }
return unless cart_has_unstackable
errors.add(:base, "#{coupon_code} removed - not stackable with other coupons")
end
def handle_user_single_use
return unless coupon.usages
usages = coupon.usages.select { |cu| cu["user_id"] == user.id }
return if usages.blank?
errors.add(:base, "cant be used more than once")
end
def handle_store_single_use
return if coupon.usages.blank?
errors.add(:base, "cant be used more than once")
end
def handle_already_in_cart
cart_has_coupon = current_coupons_in_cart.any? { |i| i['parent']['code'] == coupon_code }
return unless cart_has_coupon
errors.add(:base, "cant be used more than once")
end
end
module QueryUtils
def query_as_json(query, name)
[
'(select array_to_json(array_agg(row_to_json(row)))',
"from (#{query.to_sql}) row) as #{name}"
].join(" ")
end
def query_as_array(query, column, name)
[
"ARRAY(select #{column}",
"from (#{query.to_sql}) row) as #{name}"
].join(" ")
end
end
module Store::ValidationCoupon
def coupon
@_coupon ||= Store::Coupon
.select(coupon_query_selects)
.find_by(code: coupon_code)
end
def coupon_usages
Store::CouponUsage.joins(:coupon)
.where('coupons.code = ?', coupon_code)
end
def coupon_query_selects
[
'coupons.*',
query_as_json(coupon_usages, :usages),
].join(", ")
end
end
module Store::ValidationUser
def user
@_user ||= User
.select(user_query_selects)
.joins(:trait, :cart)
.find_by('users.id = ?', user_id)
end
def user_query_selects
[
'users.*', 'user_traits.values',
'user_traits.price_level_id',
'carts.data as cart_data',
query_as_array(user_orders, :id, :plucked_order_ids),
].join(", ")
end
def user_orders
@_user_orders ||= Store::Order.where(user_id: user_id)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment