Posted by Vince Wadhwani on Sep 16, 2008

Something I'm asked to do every once in a while is count how many times somebody has viewed a particular page. There are ways to parse logs in order to come up with those numbers and that's probably the way to go if you know what you're doing. But, if you want a quick and dirty way then you have a method called increment_counter which you'll want to use. Allow me to demonstrate it.

Before I do that though, first let's take a look at the super easy way to go. Just add a field to your table called popularity for example, and add 1 to it every time the show method is called.

def show
  @person = Person.find(params[:id])
  @person.popularity += 1
  @person.save
end

Straight forward, eh? You bet, but we can do better using the increment_counter method.

def show
  @person = Person.find(params[:id])
  Person.increment_counter(:popularity, person.id)
end

Hey, we saved ourselves one line of code! But that's not all that happened. Let's fire up script/console and take a look at some interesting side effects.

@person = Person.find(:first)

 #<Person id: 38764, name: "Vince", popularity: 44, created_at: "2006-04-17 01:36:00", updated_at: "2006-04-17 01:36:00">

@person.popularity
=> 44

@person.updated_at 
=>  Mon Apr 17 01:36:00 -0400 2006

@person.popularity += 1
=> 45

@person.save
=> true

@person.updated_at
=> Tue Sep 16 10:56:00 -0400 2008

So we updated the popularity by one and the updated_at record naturally got updated as well. That's fine and all, but it can be a pain if you've got a method that looks at recently updated people. You don't want to be updating that record every time somebody *looks* at a page.

Now we could just create another datetime field in the database which doesn't get auto-updated by rails but that's not a great solution. Why add more data for no reason? Instead, let's see what increment counter does.

@person = Person.find(:first)

 #<Person id: 38764, name: "Vince", popularity: 45, created_at: "2006-04-17 01:36:00", updated_at: "2008-09-16 10:56:00">

@person.popularity
=> 45

@person.updated_at
=> Tue Sep 16 10:56:00 -0400 2008

Person.increment_counter(:popularity, @person.id)
=> 1

@person.popularity
=> 45

Uh oh.. did it not work? Hmm.. we did increment the counter so what gives? Did we forget to save? Nope, let's check the database again and refetch the record.

@person = Person.find(:first)

 #<Person id: 38764, name: "Vince", popularity: 46, created_at: "2006-04-17 01:36:00", updated_at: "2008-09-16 10:56:00">

@person.popularity
=> 46

@person.updated_at
=> Tue Sep 16 10:56:00 -0400 2008

Bingo, so our record is updated in the datbase but the callback that changes the updated_at field wasn't used. So we saved ourselves a line of code and speeded up things just a smidge as well.