Hello everyone.
Today I will show how to implement a viewing system for any model of your Rails application.
Suppose we have a model News and we would like to keep track of how many times a single news has been displayed, in order to implement box like "the most 'seen'" etc..
Suppose we have a model News created in this way:
myapp/db/migrate/001_create_news.rb:
class CreateNews < ActiveRecord::Migration
def self.up
create_table :news do |t|
t.string :title
t.text :content
t.date :online_date_start, :null => true
t.date :online_date_end, :null => true
t.boolean :online, :null => false, :default => true
t.timestamps
end
end
def self.down
drop_table :news
end
end
myapp/app/models/news.rb:
class News < ActiveRecord::Base
validates_length_of :title, :within => 2..255
validates_presence_of :title, :content, :online_date_start
end
The basic idea is to increment a counter displayed every time a user views the news.
Some considerations:
an implementation of this type would allow a user to press the refresh button and constantly increase the counter.
So the goal is to have a clever mechanism that increases the counter:
- 1 once for each registered user
- 1 only once per IP in the case of guest
We would also like that this mechanism could be applied not only to model News, but to any model of our application.
Then we create the migration of our model Viewing:
myapp/db/migrate/002_create_viewings.rb:
class CreateViewings < ActiveRecord::Migration
def self.up
create_table :viewings do |t|
t.string :ip
t.string :viewable_type
t.integer :viewable_id
t.references :person
t.datetime :created_at
end
end
def self.down
drop_table :viewings
end
end
and the corresponding model:
myapp/app/models/viewing.rb:
class Viewing < ActiveRecord::Base
# RELATIONSHIPS
belongs_to :viewable, :polymorphic => true, :counter_cache => :popularity
belongs_to :viewer, :class_name => "Person", :foreign_key => "person_id"
# VALIDATIONS
validates_uniqueness_of :ip, :scope => [:viewable_id, :viewable_type, :person_id]
validates_uniqueness_of :person_id, :allow_nil => true
# OTHER
def viewable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
As you can see, the model Viewing is not closely tied to model News, but given its polimorphic nature, it may be associated with any model.
At this point, we can associate the model Viewing to the News :
myapp/app/models/news.rb:
class News < ActiveRecord::Base
validates_length_of :title, :within => 2..255
validates_presence_of :title, :content, :online_date_start
has_many :viewings, :as => :viewable
end
and add the column "counter cache to the table News:
myapp/db/migrate/003_add_popularity_to_news.rb:
class AddPopularityToNews < ActiveRecord::Migration
def self.up
add_column :news, :popularity, :integer, :default => 0
end
def self.down
remove_column :news, :popularity
end
end
Now we have everything necessary to implement the mechanism for viewing in our model News.
Let's show how to increment the counter to "show" of the news.
myapp/app/controllers/news_controller.rb:
class NewsController < ApplicationController
after_filter :record_view, :only => :show
def show
@news = News.find(params[ :id ])
end
private
def record_view
@news.viewings.create(:ip => request.remote_ip, :viewer => current_user) unless @news.nil?
end
end