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()
Hey Ana, this worked really well, thanks. Very nicely presented.