How to implement a viewing system in Rails
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:
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:
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:
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:
# 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:
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:
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:
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