SyntaxHighlighter

Friday, August 02, 2013

Cookie Collection Objects

Web developers will often have situations where we want to store something, usually a collection, temporarily - like the last few pages a user has visited, or a list of things to compare.  In my case, I wanted to store recent restaurants - pages the user visited, so they could return to them later.  This is often accomplished with cookies, but this is a way to hide the cookies and simply interact with the collection.

Using cookies to store and retrieve

At it's simplest, a cookie collection object needs to do the following.

Add items
Retrieve all items

In this example, I want to be able to show a list of recent restaurants on the side of the page.

Do do this, I would like an interface that allows me to add a recent restaurant.

 recent_restaurants.push(@restaurant)

Or, to retrieve recent restaurants...

 recent_restaurants

I'd like to avoid making direct calls using the 'cookies' hash, instead interact with the collection and hold the list between requests in the cookie.

With a CookieCollection class, we can use this to handle the common additions and deletes.  We also get all the power that comes with the Array class.  This class is meant to be subclassed.

class CookieCollection < Array

  attr_accessor :ids

  def initialize(cookies)
    super(0)
    @_cookies = cookies
    if @_cookies[cookie_name].present?
      self.ids = @_cookies[cookie_name].split(',')
    else
      self.ids = []
    end
  end

  def push(object)
    super(object) 
    update_cookie
  end

  private

  def update_cookie
    ids = map(&:id)
    @_cookies[cookie_name] = {:value => ids.join(','), :expires => 20.years.from_now}
  end

  def cookie_name
    self.class.name.parameterize
  end

end

Note - the cookie value is only the ids of the object.

In a RecentRestaurants subclass, we can make the specific updates to our list of recent restaurants. In this case, I wanted to limit my list to the last 5 items.

class RecentRestaurants < CookieCollection

  def initialize(cookies)
    super(cookies)
    self.ids = ids.last(5)
    ids.each{ |r_id| push(Restaurant.find(r_id)) }
  end

  def push(restaurant)
    delete(restaurant)
    while length > 4
      delete_at(0)
    end
    super(restaurant)
  end

end

In the application controller, I exposed a helper method to allow access to recent_restaurants

 
  def recent_restaurants
    @_recent_restaurants ||= RecentRestaurants.new(cookies)
  end
  helper_method :recent_restaurants

And then, in a view or controller, you can easily do things like

class RestaurantsController < ApplicationController
  def show
    @restaurant = Restaurant.find(params[:id])

    # Add a restaurant
    recent_restaurants.push(@restaurant)

    # Access all recent restaurants easily
    recent_restaurants
  end
end

This cleans things up quite a bit and makes it easier to work with in a consistent way.  It could easily be extended to be used with Sessions, as well.

Monday, July 29, 2013

A Bookmarklet for switching between localhost and production

If you are often going between production and localhost:3000 - you can easily change the domain name with a simple javascriptlet - something like this...

javascript:a=document.location.href.match(/http:\/\/([^\/]+)\/(.+)/);if(a[1].match(%22exampledomain%22)){document.location=%22http://localhost:3000/%22%20+%20a[2]}else{document.location=%22http://www.exampledomain.com/%22%20+%20a[2]}

 Make this the content of a bookmark, and add it to your bookmark menu.  Easy flipping.

Adjust further if you need to go between a staging environment or another localhost domain.