Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
The original gem creates a new read mark whenever a "reader" is created. That way, this new user is not going to be able to read past messages, that could be important for him. So this fork avoid the creation of that read mark, so the user have access to all messages, from the beginning.
- Manages unread records for anything you want users to read (like messages, documents, comments etc.)
- Supports mark as read to mark a single record as read
- Supports mark all as read to mark all records as read in a single step
- Gives you a scope to get the unread records for a given user
- Needs only one additional database table
- Most important: Great performance
- Ruby 1.8.7 or 1.9.x
- Rails >= 2.3.6 (including 3.0, 3.1, 3.2)
- Tested with SQLite and MySQL
- Needs a timestamp field in your models (e.g. created_at) with a database index on it
Step 1: Add this to your Gemfile:
gem 'unread'
and run
bundle
Step 2: Add this migration:
class CreateReadMarks < ActiveRecord::Migration
def self.up
create_table :read_marks, :force => true do |t|
t.integer :readable_id
t.integer :user_id, :null => false
t.string :readable_type, :null => false, :limit => 20
t.datetime :timestamp
end
add_index :read_marks, [:user_id, :readable_type, :readable_id]
end
def self.down
drop_table :read_marks
end
endand run the migration:
rake db:migrate
class User < ActiveRecord::Base
acts_as_reader
end
class Message < ActiveRecord::Base
acts_as_readable :on => :created_at
end
message1 = Message.create!
message2 = Message.create!
## Get unread messages for a given user
Message.unread_by(current_user)
# => [ message1, message2 ]
message1.mark_as_read! :for => current_user
Message.unread_by(current_user)
# => [ message2 ]
## Get all messages including the read status for a given user
messages = Message.with_read_marks_for(current_user)
# => [ message1, message2 ]
messages[0].unread?(current_user)
# => false
messages[1].unread?(current_user)
# => true
Message.mark_as_read! :all, :for => current_user
Message.unread_by(current_user)
# => [ ]
# Optional: Cleaning up unneeded markers
# Do this in a cron job once a day.
Message.cleanup_read_marks!The main idea of this gem is to manage a list of read items for every reader after a certain timestamp.
The gem defines a scope doing a LEFT JOIN to this list, so your app can get the unread items in a performant manner. Of course, other scopes can be combined.
It will be ensured that the list of read items will not grow up too much:
- If a user uses "mark all as read", his list gets deleted and the timestamp is set to the current time.
- If a user never uses "mark all as read", the list will grow and grow with each item he reads. But there is help: Your app can use a cleanup method which removes unnecessary list items.
Overall, this gem can be used for large data. Please have a look at the generated SQL queries, here is an example:
# Assuming we have a user who has marked all messages as read on 2010-10-20 08:50
current_user = User.find(42)
# Get the unread messages for this user
Message.unread_by(current_user)Generates query:
SELECT messages.*
FROM messages
LEFT JOIN read_marks ON read_marks.readable_type = 'Message'
AND read_marks.readable_id = messages.id
AND read_marks.user_id = 42
AND read_marks.timestamp >= messages.created_at
WHERE read_marks.id IS NULL
AND messages.created_at > '2010-10-20 08:50:00'Hint: You should add a database index on messages.created_at.
There are two other gems/plugins doing a similar job:
Unfortunately, both of them have a lack of performance, because they calculate the unread records doing a find(:all), which should be avoided for a large amount of records. This gem is based on a timestamp algorithm and therefore it's very fast.
Copyright (c) 2010,2012 Georg Ledermann, released under the MIT license
