Hank Stoever
part time nerd, part time gnar.

Asynchronously Counting Page Views with Rails

posted over 4 years ago - 1 min read

Tracking page views really isn't too difficult with Ruby on Rails. Consider a blog with a Post model, which has a views column.

in app/models/post.rb:

def increment(by = 1)
  self.views ||= 0
  self.views += by
  self.save
end

Then in app/controllers/post_controller.rb:

def show
  @post = …  
  @post.increment
  …
end

This is very simple, and it works. But it also means that for every request, we have to do an extra SQL query to update the post with the new view count. For a blog, we want to optimize for fast page loads, so it would be better to move this work into an asynchronously queue.

Afterparty is a queue that is compatible with Rails 3 and 4. It was designed to be compatible with the rails 4 queueing api and supports executing jobs in the future.

After you install Afterparty, change posts_controller to increment asynchronously:

def show
  @post = …
  job = Afterparty::BasicJob.new @post, :increment
  Rails.configuration.queue << job
  …
end

Using Afterparty::BasicJob will create a simple job that accepts an object, a method, and any number of arguments. When the job is run, the method will simply be called on the object. Make sure you have a worker queue running by executing rake jobs:work. The next time you view that post, the views value will increment in the background.

This absolutely has some trade-offs. For one, you don't display the exact count of views when a visitor views a post. This is because the value is being updated simultaneously as the page is rendered. You can account for this by displaying @post.views + 1, but you still can't guarantee that it's accurate, as there may be other background jobs also about to increment the views of this post. The advantage of this approach is that page requests will be much faster. If you're in a situation like this where you want to count something and care about fast requests more than precise counts, I encourage you to try this pattern.

comments powered by Disqus