Skip to content

Instantly share code, notes, and snippets.

@arches
Last active December 29, 2015 10:59
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 arches/7660643 to your computer and use it in GitHub Desktop.
Save arches/7660643 to your computer and use it in GitHub Desktop.
hash-backed shopping cart w session default
require 'active_support/hash_with_indifferent_access'
class Cart
include Enumerable
attr_writer :storage
def each(&blk)
deal_ids.each(&blk)
end
def add(deal_id, options={})
deal_hash[deal_id.to_i] = HashWithIndifferentAccess.new(options)
remove(deal_id) if options_for(deal_id.to_i)[:quantity] == 0
store
end
def remove(deal_id)
deal_hash.delete(deal_id.to_i)
store
end
def deal_ids
deal_hash.keys
end
def total
Deal.find(deal_ids).inject(0) { |sum, deal| sum + (deal.price * options_for(deal.id).fetch(:quantity){1}) }
end
def options_for(deal_id)
deal_hash[deal_id]
end
def empty?
deal_ids.length == 0
end
def clear!
deal_ids.collect{|id| remove(id)}
end
def size
deal_ids.length
end
private
def deal_hash
@deal_hash ||= storage[:cart]
@deal_hash ||= {}
end
def store
storage[:cart] = deal_hash
end
def storage
@storage ||= {}
end
end
@arches
Copy link
Author

arches commented Nov 26, 2013

The magic happens in the @storage variable. By default, it's just a hash. So I can do something like this out of the box:

c = Cart.new
c.add("tennis_balls", {quantity: 12})
c.size # => 1

But I can use any hash I want, doesn't have to be a new hash. What else is a hash? The session!

def add_to_cart
  c = Cart.new
  c.storage = session
  c.add("tennis_balls", {quantity: 12})

session[:cart] # => {"tennis_balls" => {quantity: 12}}

### the user makes some more requests, finally lands on my checkout page

def checkout
  c = Cart.new
  c.storage = session # tennis balls are still in there!
  c.size # => 1

It's just dependency-injection, but the fact that hashes have such small interfaces make it easy to swap out other things. For example, memcache:

c = Cart.new
c.storage = Rails.cache # system-wide cart

Or write an adapter!

require 'json'

class FileHash
  attr_accessor :backing_hash, :file_name

  def initialize(file_name)
    self.file_name = file_name
    File.open(file_name, "w") { |f| f.write "{}" } unless File.exist?(file_name)
    read
  end

  def []=(key, value)
    backing_hash[key] = value
    write
  end

  def [](key)
    backing_hash[key]
  end

  def write
    f = File.open(file_name, 'w')
    f.write backing_hash.to_json
    f.close
    read
  end

  def read
    f = File.open(file_name, 'r')
    self.backing_hash = JSON.parse(f.read)
    f.close
    backing_hash
  end
end

So now we have file-system persisted carts

def add_to_cart
  file_name = "cart_for_#{current_user.id}.json"
  c = Cart.new
  c.storage = FileHash.new(file_name)
  c.add("tennis_balls", {quantity: 12})


$ cat cart_for_123.json
{"cart":{"tennis_balls":{"quantity":12}}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment