An open API service indexing awesome lists of open source software.

https://github.com/grimen/is_visitable

Rails: Track unique and total visits/viewings of an ActiveRecord resource based on user/account or IP.
https://github.com/grimen/is_visitable

Last synced: 22 days ago
JSON representation

Rails: Track unique and total visits/viewings of an ActiveRecord resource based on user/account or IP.

Awesome Lists containing this project

README

        

h1. IS_VISITABLE ~ concept version ~

_Rails: Track unique and total visits/viewings of an ActiveRecord resource based on user/account or IP._

h2. Installation

*Gem:*

sudo gem install is_visitable

and in @config/environment.rb@:

config.gem 'is_visitable'

*Plugin:*

./script/plugin install git://github.com/grimen/is_visitable.git

h2. Usage

h3. 1. Generate migration:


$ ./script/generate is_visitable_migration

Generates @db/migrations/{timestamp}_is_visitable_migration@ with:


class IsVisitableMigration < ActiveRecord::Migration
def self.up
create_table :visits do |t|
t.references :visitable, :polymorphic => true

t.references :visitor, :polymorphic => true
t.string :ip, :limit => 24

t.integer :visits, :default => 1

# created_at <=> first_visited_at
# updated_at <=> last_visited_at
t.timestamps
end

add_index :visits, [:visitor_id, :visitor_type]
add_index :visits, [:visitable_id, :visitable_type]
end

def self.down
drop_table :visits
end
end

h3. 2. Make your model count visits:


class Post < ActiveRecord::Base
is_visitable
end

or, with explicit visitor (or visitors):


class Post < ActiveRecord::Base
# Setup associations for the visitor class(es) automatically.
is_visitable :by => [:users, :ducks]
end

h3. 3. ...and here we go:

Examples:


@post = Post.create

@post.visited? # => false
@post.unique_visits # => 0
@post.total_visits # => 0

@post.visit!(:by => '128.0.0.0')
@post.visit!(:by => @user) # aliases: :user, :account

@post.visited? # => true
@post.unique_visits # => 2
@post.total_visits # => 2

@post.visit!(:by => '128.0.0.0')
@post.visit!(:by => @user)
@post.visit!(:by => '128.0.0.1')

@post.unique_visits # => 3
@post.total_visits # => 5

@post.visited_by?('128.0.0.0') # => true
@post.visited_by?(@user) # => true
@post.visited_by?('128.0.0.2') # => false
@post.visited_by?(@another_user) # => false

@post.reset_visits!
@post.unique_visits # => 0
@post.total_visits # => 0

# Note: See documentation for more info.

h2. Mixin Arguments

The @is_visitable@ mixin takes some hash arguments for customization:

* @:by@ - the visitor model(s), e.g. User, Account, etc. (accepts either symbol or class, i.e. @User@ <=> @:user@ <=> @:users@, or an array of suchif there are more than one visitor model). The visitor model will be setup for you. Note: Polymorhic, so it accepts any model. Default: @nil@.
* @:accept_ip@ - accept anonymous users uniquely identified by IP (well...you handle the bots =D). See examples below how to use this as your visitor object. Default: @false@.

h2. Aliases

To make the usage of IsVistable a bit more generic (similar to other plugins you may use), there are two useful aliases for this purpose:

* @Visit#owner@ <=> @Visit#visitor@
* @Visit#object@ <=> @Visit#visitable@

Example:


@post.visits.first.owner == post.visits.first.visitor # => true
@post.visits.first.object == post.visits.first.visitable # => true

h2. Finders (Named Scopes)

IsVisitable has plenty of useful finders implemented using named scopes. Here they are:

h3. @Visit@

*Order:*

* @in_order@ - most recent visits last (order by creation date).
* @most_recent@ - most recent visits first (opposite of @in_order@ above).
* @least_visits@ - visits with least total visits first.
* @most_rating@ - visits with most total visits first.

*Filter:*

* @limit()@ - maximum @@ visits.
* @since()@ - visits since @@.
* @recent()@ - if DateTime: visits since @@, else if Fixnum: pick last @@ number of visits.
* @between_dates(, to_date)@ - visits between two datetimes.
* @with_visits()@ - visits with(in) visits value (or range) @@.
* @of_visitable_type()@ - visits of @@ type of visitable models.
* @by_visitor_type()@ - visits of @@ type of visitor models.
* @on()@ - visits on the visitable object @@ .
* @by()@ - visits by the @@ type of visitor models.

h3. @Visitable@

_TODO: Documentation on named scopes for Visitable._

h3. @Visitor@

_TODO: Documentation on named scopes for Visitor._

h3. Examples using finders:


@user = User.first
@post = Post.first

@post.visits.recent(10) # => [10 most recent visits]
@post.visits.recent(1.week.ago) # => [visits since 1 week ago]

@post.visits.with_visits(100..500) # => [all visits on @post with total visits between 100 and 500]

@post.visits.by_visitor_type(:user) # => [all visits on @post by User-objects]
# ...or:
@post.visits.by_visitor_type(:users) # => [all visits on @post by User-objects]
# ...or:
@post.visits.by_visitor_type(User) # => [all visits on @post by User-objects]

@user.visits.on(@post) # => [all visits by @user on @post]
@post.visits.by(@user) # => [all visits by @user on @post] (equivalent with above)

Visit.on(@post) # => [all visits on @user] <=> @post.visits
Visit.by(@user) # => [all visits by @user] <=> @user.visits

# etc, etc. It's all named scopes, so it's really no new hokus-pokus you have to learn.

h2. Additional Methods

*Note:* See documentation (RDoc).

h2. Extend the Visit model

This is optional, but if you wanna be in control of your models (in this case @Visit@) you can take control like this:


class Visit < IsVisitable::Visit

# Do what you do best here... (stating the obvious: core IsVisitable associations, named scopes, etc. will be inherited)

end

h2. Caching

If the visitable class table - in the sample above @Post@ - contains a columns @cached_total_visits_count@ and @cached_unique_visits@, then a cached value will be maintained within it for the number of unique and total visits the object have got. This will save a database query for counting the number of visits, which is a common task.

Additional caching fields:


class AddTrackVisitsCachingToPostsMigration < ActiveRecord::Migration
def self.up
# Enable is_visitable-caching.
add_column :posts, :cached_unique_visits, :integer
add_column :posts, :cached_total_visits, :integer
end

def self.down
remove_column :posts, :cached_unique_visits
remove_column :posts, :cached_total_visits
end
end

h2. Example

h3. In your "visitable resource" controller:

Example: @app/controllers/posts_controller.rb@:


class PostsController < ApplicationController

def show
...
@post.visit!(:by => (current_user.present? ? current_user : request.try(:remote_ip)))
...
end

end

h2. Dependencies

For testing: "shoulda":http://github.com/thoughtbot/shoulda, "redgreen":http://gemcutter.org/gems/redgreen, "acts_as_fu":http://github.com/nakajima/acts_as_fu, and "sqlite3-ruby":http://gemcutter.org/gems/sqlite3-ruby.

h2. Notes

* Tested with Ruby 1.8.6 - 1.9.1 and Rails 2.3.2 - 2.3.4.
* Let me know if you find any bugs; not used in production yet so consider this a concept version.

h2. TODO

* documentation: A few more README-examples.
* helper: Controller helper taking arguments for DRYer controller code. Example (in controller): handle_visits :by => current_user
* feature: Useful finders for @Visitable@.
* feature: Useful finders for @Visitor@.
* testing: More thorough tests.
* refactor: Refactor generic stuff to new gem, @is_base@, and add as gem dependency. Reason: Share the same patterns for my very similar ActiveRecord plugins: is_reviewable, is_visitable, is_commentable, and future additions.

h2. License

Released under the MIT license.
Copyright (c) "Jonas Grimfelt":http://github.com/grimen