Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

simple hit counter for page views in rails

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

like image 545
holden Avatar asked Jan 27 '11 11:01

holden


1 Answers

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 
like image 142
johnmcaliley Avatar answered Oct 18 '22 20:10

johnmcaliley