simple hit counter for page views in rails
Asked Answered
S

1

38

I've found several solutions for this problem, for example railstat from this post:

Page views in Rails

I have a bunch of articles and reviews which I would like a hit counter filtered by unique IPs. Exactly like Stackoverflow does for this post. But I don't really care for such a solution as railstat when google analytics is already doing this for me and including a whole lot of code, keeping track of unique IPs, etc.. My present thinking is to use Garb or some other Analytics plugin to pull the pages stats if they are older than say 12 hours updating some table, but I also need a cache_column.

I'm assuming you can pull stats from Analytics for a particular page and that they update their stats every 12 hours?

I'm wondering if there are any reasons why this would be a bad idea, or if someone has a better solution?

Thanks

Surfperch answered 27/1, 2011 at 11:26 Comment(0)
A
94

UPDATE

The code in this answer was used as a basis for http://github.com/charlotte-ruby/impressionist Try it out


It would probably take you less time to code this into your app then it would to pull the data from Analytics using their API. This data would most likely be more accurate and you would not have to rely an an external dependancy.. also you would have the stats in realtime instead of waiting 12 hours on Analytics data. request.remote_ip works pretty well. Here is a solution using polymorphism. Please note that this code is untested, but it should be close.

Create a new model/migration to store your page views (impressions):

class Impressions < ActiveRecord::Base
  belongs_to :impressionable, :polymorphic=>true 
end

class CreateImpressionsTable < ActiveRecord::Migration
  def self.up
    create_table :impressions, :force => true do |t|
      t.string :impressionable_type
      t.integer :impressionable_id
      t.integer :user_id
      t.string :ip_address
      t.timestamps
    end
  end

  def self.down
    drop_table :impressions
  end
end

Add a line to your Article model for the association and add a method to return the impression count:

class Article < ActiveRecord::Base
  has_many :impressions, :as=>:impressionable

  def impression_count
    impressions.size
  end

  def unique_impression_count
    # impressions.group(:ip_address).size gives => {'127.0.0.1'=>9, '0.0.0.0'=>1}
    # so getting keys from the hash and calculating the number of keys
    impressions.group(:ip_address).size.keys.length #TESTED
  end
end

Create a before_filter for articles_controller on the show action:

before_filter :log_impression, :only=> [:show]

def log_impression
  @article = Article.find(params[:id])
  # this assumes you have a current_user method in your authentication system
  @article.impressions.create(ip_address: request.remote_ip,user_id:current_user.id)
end

Then you just call the unique_impression_count in your view

<%[email protected]_impression_count %>

If you are using this on a bunch of models, you may want to DRY it up. Put the before_filter def in application_controller and use something dynamic like:

impressionable_class = controller_name.gsub("Controller","").constantize
impressionable_instance = impressionable_class.find(params[:id])
impressionable_instance.impressions.create(ip_address:request.remote_ip,user_id:current_user.id)

And also move the code in the Article model to a module that will be included in ActiveRecord::Base. You could put the send include in a config/initializer.. or if you want to get crazy, just turn the whole thing into a rails engine, so you can reuse on other apps.

module Impressionable
  def is_impressionable
    has_many :impressions, :as=>:impressionable
    include InstanceMethods
  end
  module InstanceMethods
    def impression_count
      impressions.size
    end

    def unique_impression_count
      impressions.group(:ip_address).size
    end
  end
end

ActiveRecord::Base.extend Impressionable
Alexandria answered 27/1, 2011 at 14:28 Comment(9)
I've tried your plugin and works like a charm. Thanks for sharing !Lowell
@cowboycoded Can You add mongoid support for this plugin please?Roderick
@Roderick support for mongo_mapper was added by another committer a few months ago. I am really not that familiar with mongo, but if you want to give a try at adding it to the plugin, you can follow the changes made in this commit and add a new impression model that supports mongoid: github.com/tute/impressionist/commit/…Alexandria
to make it work, just from glancing at the commits in the mongo_mapper pull request it looks like you would need to add a generator for mongoid, add a condition to engine.rb to test for mongoid orm, and then add a mongoid specific impression.rb and impressionable.rbAlexandria
@cowboycoded would impressionist(@widget... work for a collection as well as it's working with a single record? great plugin BTW.Shepperd
@Shepperd no because it expects a single record. The gem could be modified to add impressions for all records in the collection, but it does not currently work that way.Alexandria
Wouldn't it make more sense to log the impression in an after_filter ? That way you can avoid any clashes with other filters that are scheduled to execute, e.g., the friendly id gem. Also this way you can avoid logging an impression if any user authorization redirects the user somewhere else if they don't have sufficient rights for a page. Edit: Whoah, de-ja-vu, looks like I already opened a ticket for this github.com/charlotte-ruby/impressionist/issues/50Oleomargarine
@cowboycoded is there anyway to measure/capture the time a user spent on a page with your gem too?Leslie
For unique_impression_count, .length() works for me since the .group() function is returning a hash.Hypercriticism

© 2022 - 2024 — McMap. All rights reserved.