Dave says:

I’d put a method in my controller to increment the counter. It might look something like


def increment_count
  if session[:counter].nil?
    session[:counter] = 0
  end
  session[:counter] += 1
end
However, a more idiomatic way in Ruby is to do

def increment_count
  session[:counter] ||= 0
  session[:counter] += 1
end

(Yes, it can be done in one line, using a semi-colon as a delimiter, but I think the above is clearer)

Then, I’d set an instance variable to this value in each of the actions I wanted to count

def index
  # ...
  @count = increment_count
end

Mark says:

that would sum all the counted actions though, wouldn’t it – as the session :count variable can’t differentiate between actions. so if you’d viewed the index action 3 times and the add_to_cart action twice, the count would show as 5.

Chas says:

No Mark, it wouldn’t. Dave has placed the call to increment_count inside the index action and not the add_to_cart action, the result is increment_count is only called when the index action has been called.

Aaron says:

So would doing something like this be ‘wrong’?


       def index
                @products = Product.find_products_for_sale
                @date = Date.today
                @time = Time.now

                #initialize
                if session[:counter].nil?
                        session[:counter] = 0
                else
                        session[:counter] += 1
                end
        end

Chris says:

Aaron,

There are two issues with that code. The main problem is that if someone views the index for the first time, their count will be 0 instead of 1. You can either remedy this by setting the counter variable to 1 instead of 0, or pull the counter incremental outside of the if/else construct.

The second issue, which is more minor, is that it’s one of those bits of code that feels like it should be pulled out into its own private method. If you ever decide to expand it to another action, then pulling it out will make the work nice and DRY. (Of course, you could refactor when the need arises) I’m pretty poor at explaining myself on why it would be separate, and in reality, it could be contained within the index action, but my general rule of thumb is that if I think I might be served by making a method out of it, then I always do.

Benjamin asserts:

This is what I tried, and it works pretty well. I understand the appeal of creating another function to handle the counter setting, but I prefer not to when we’re only using it once. If we used the counter functionality on other pages, I would probably abstract it like mentioned above.

def index
    session[:counter] ||= 0
    @count = session[:counter] += 1
    @products = Product.find_products_for_sale
end
The first line of the action takes advantage of assignment chaining. It sets session[:counter] to 0 if session[:counter] doesn’t exist (is equal to nil). It then adds one to session[:counter] and sets @count to that new value.

Keep in mind there is still a required reset later on in the add_to_cart action, but this takes care of the initialization and increment.

Claudio says:

Personally, I like to extract the method (I called mine increment_index_access_counter) to simply reveal the intention of the code and hide the pure technicality of having to initialize the session counter. I think it reads better when you avoid mixing business objects (product), with somewhat technical aspects (session[:counter]) in the same method.

Wael says:

What I did was pretty simple, I added an increment_counter function, a protected one with the same code mentioned here.


def increment_counter
  if session[:counter].nil?
    session[:counter] = 0
  end
  session[:counter] += 1
end

In the index action, I called the increment_counter, like this


def index
  increment_counter
  ...
In the view I use this code

<%= pluralize session[:counter], 'time' %>

Since the view can access the session variable, There’s no need to make an instance variable, or should we?

Bill adds:

The choice of where to initialize seems to depend on where we will use the value. Most folks seem to interpret D3 as showing the value in the store index template (rather than in the banner for example). If we display outside of the store view, I would think we want to initialize outside the store controller?

So I initialized in ApplicationController:


class ApplicationController < ActionController::Base
  protect_from_forgery

  private

  before_filter :initialize_session

  def initialize_session
  # Called before any controller method
    session[:store_index_count] ||= 0
  end

# ...

The upside is that I can now reference the value anywhere. The downside is that this code is executed for every request and the value is stored in all sessions (session bloat is not your friend!).

Thoughts?