module Effective class Cart < ActiveRecord::Base self.table_name = (EffectiveOrders.carts_table_name || :carts).to_s belongs_to :user, polymorphic: true, optional: true # Optional. We want non-logged-in users to have carts too. has_many :cart_items, -> { order(:id) }, inverse_of: :cart, dependent: :delete_all accepts_nested_attributes_for :cart_items effective_resource do cart_items_count :integer timestamps end scope :deep, -> { includes(cart_items: :purchasable) } # cart.add(@product, unique: -> (a, b) { a.kind_of?(Product) && b.kind_of?(Product) && a.category == b.category }) # cart.add(@product, unique: :category) # cart.add(@product, unique: false) # Add as many as you want def add(item, quantity: 1, unique: true) raise 'expecting an acts_as_purchasable object' unless item.kind_of?(ActsAsPurchasable) existing = ( if unique.kind_of?(Proc) cart_items.find { |cart_item| instance_exec(item, cart_item.purchasable, &unique) } elsif unique.kind_of?(Symbol) || (unique.kind_of?(String) && unique != 'true') raise "expected item to respond to unique #{unique}" unless item.respond_to?(unique) cart_items.find { |cart_item| cart_item.purchasable.respond_to?(unique) && item.send(unique) == cart_item.purchasable.send(unique) } elsif unique.present? find(item) end ) if existing if unique || (existing.unique.present?) existing.assign_attributes(purchasable: item, quantity: quantity, unique: existing.unique) else existing.quantity = existing.quantity + quantity end end if item.quantity_enabled? && (existing ? existing.quantity : quantity) > item.quantity_remaining raise EffectiveOrders::SoldOutException, "#{item.purchasable_name} is sold out" end existing ||= cart_items.build(purchasable: item, quantity: quantity, unique: (unique.to_s unless unique.kind_of?(Proc))) save! end def clear! cart_items.each { |cart_item| cart_item.mark_for_destruction } cart_items.present? ? save! : true end def remove(item) find(item).try(:mark_for_destruction) save! end def includes?(item) find(item).present? end def find(item) cart_items.find { |cart_item| cart_item == item || cart_item.purchasable == item } end def purchasables cart_items.map { |cart_item| cart_item.purchasable } end def size cart_items_count || cart_items.length end def present? size > 0 end def blank? size <= 0 end def subtotal cart_items.map { |ci| ci.subtotal }.sum end end end