Skip to content

Instantly share code, notes, and snippets.

@mrdev023
Forked from anaclair/shopping_cart.md
Created March 22, 2023 11:46
Show Gist options
  • Save mrdev023/6ad9ee3ec1abb85738f89aeaee674cf7 to your computer and use it in GitHub Desktop.
Save mrdev023/6ad9ee3ec1abb85738f89aeaee674cf7 to your computer and use it in GitHub Desktop.
Implementing Redis Shopping Cart

What is Redis?
Redis is an open source, advanced in-memory key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.

######1. If you're running OS X, install redis in terminal using Homebrew : brew install redis

######2. Add the redis and hiredis gems to the project's Gemfile (remember to bundle install after you do this) :

gem 'redis'
gem 'hiredis'

What is hiredis?
By default, redis uses Ruby’s socket library to talk to Redis. Because we will have some large replies (such as SMEMBERS list command), we use an alternative connection driver called hiredis which optimizes for speed, at the cost of portability.

######3. Open a new tab in terminal and start the redis server : redis-server

######4. Create a file, generocity/config/initializers/redis.rb where we’ll set up our Redis connection as a global variable. We specify hiredis as the driver when instantiating the client object. In generocity/config/initializers/redis.rb :

$redis = Redis.new(:driver => :hiredis)

######5. In terminal, generate a carts controller with a show action : rails g controller carts show

######6. In routes.rb, declare cart as a singular resource (one per user) and define routes for adding and removing items from the cart :

resource :cart, only: [:show] do
  put 'add/:item_id', to: 'carts#add', as: :add_to
  put 'remove/:item_id', to: 'carts#remove', as: :remove_from
end

######7. In CartsController, we’ll use the SMEMBERS, SADD, and SREM Redis commands to list, add, and remove item ids into a unique set named current_user_cart, identified with current_user.id :

class CartsController < ApplicationController
  before_action :authenticate_user!

  def show
    cart_ids = $redis.smembers current_user_cart
    @cart_items = Item.find(cart_ids)
  end

  def add
    $redis.sadd current_user_cart, params[:item_id]
    render json: current_user.cart_count, status: 200
  end

  def remove
    $redis.srem current_user_cart, params[:item_id]
    render json: current_user.cart_count, status: 200
  end

  private

  def current_user_cart
    "cart#{current_user.id}"
  end
end

######8. We’ve added :authenticate_user! as a before_action callback to restrict access for controller actions to signed-in users only. In the add & remove actions, we respond with current_user.cart_count which returns the number of items inside the current user's cart using the SCARD command. In the User model :

def cart_count
  $redis.scard "cart#{id}"
end

If you get an undefined method error when testing locally, restart the rails and redis servers!

######9. In views/layouts/_header.html.erb, add a link to the cart :

<li>
  <%= link_to cart_path do%>
    My Cart
    (<span class="cart-count"><%=current_user.cart_count%></span>)
  <%end%>
</li>

######10. In views/items/show.html.erb, create a link that handles the add and remove from cart actions :

<%if signed_in?%>
  <%=link_to "", class: "button", data: {target: @cart_action, addUrl: add_to_cart_path(@item), removeUrl: remove_from_cart_path(@item)} do%>
    <span><%=@cart_action%></span> Cart
  <%end%>
<%end%>

######11. Define @cart_action in the ItemsController show action:

def show
  @item = Item.find(params[:id])
  @cart_action = @item.cart_action current_user.try :id
end

######12. We call a cart_action method on @item which determines if the item is in the user's cart and returns the appropriate response. The SISMEMBER Redis command determines if a given value is a member of a set. In the Item model :

def cart_action(current_user_id)
  if $redis.sismember "cart#{current_user_id}", id
    "Remove from"
  else
    "Add to"
  end
end

######13. AJAXify the "Add to/Remove from Cart" link. In app/assets/javascripts/carts.coffee :

$(window).load ->
  $('a[data-target]').click (e) ->
    e.preventDefault()
    $this = $(this)
    if $this.data('target') == 'Add to'
      url = $this.data('addurl')
      new_target = "Remove from"
    else
      url = $this.data('removeurl')
      new_target = "Add to"
    $.ajax url: url, type: 'put', success: (data) ->
      $('.cart-count').html(data)
      $this.find('span').html(new_target)
      $this.data('target', new_target)

######14. In order to keep this script as simple as it is, disable Turbolinks by removing the require turbolinks line from the app/assets/javascripts/application.js file.

######15. Represent the cart items in views/carts/show.html.erb :

<div id="mycart" class="small-10 small-centered medium-8 large-8 column">
  <div class="p1 glassy-bg mb1 text-center radius-l1 radius-r1">
    <h4>My Cart</h4>
    <p class="mb0"> You've selected <span class="cart-count"><%=current_user.cart_count%></span> items!</p>
  </div>

  <% @cart_items.each do |item|%>
  <div data-equalizer class="cart-item large-12 column mb1">
    <div class="column large-2 text-center p0" data-equalizer-watch>
      <%=link_to item do%>
        <%=image_tag item.image, class: "radius-l1"%>
      <%end%>
    </div>
    <div class="column large-7 glassy-bg text-center" data-equalizer-watch>
        <p class="scale ptm"> <%= item.name %> </p>
    </div>
    <div class="column large-3 primary-bg text-center radius-r1" data-equalizer-watch>
      <%=link_to "" , class: "remove", data: {targetUrl: remove_from_cart_path(item)} do%>
        Remove From Cart
      <%end%>
      <h4 class="scale">$ <%= item.price %></h4>
    </div>
  </div>
  <%end%>
</div>

######16. AJAXify the "Remove From Cart" link in app/assets/javascripts/carts.coffee. Just add this after the other AJAX call.

  $('#mycart .remove').click (e) ->
    e.preventDefault()
    $this = $(this).closest('a')
    url = $this.data('targeturl')
    $.ajax url: url, type: 'put', success: (data) ->
      $('.cart-count').html(data)
      $this.closest('.cart-item').slideUp()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment