Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ledermann/unread
Handle unread records and mark them as read with Ruby on Rails
https://github.com/ledermann/unread
activerecord rails ruby rubygems unread-records
Last synced: 5 days ago
JSON representation
Handle unread records and mark them as read with Ruby on Rails
- Host: GitHub
- URL: https://github.com/ledermann/unread
- Owner: ledermann
- License: mit
- Created: 2010-10-21T09:22:09.000Z (about 14 years ago)
- Default Branch: master
- Last Pushed: 2024-10-05T13:42:01.000Z (2 months ago)
- Last Synced: 2024-12-01T09:42:06.310Z (11 days ago)
- Topics: activerecord, rails, ruby, rubygems, unread-records
- Language: Ruby
- Homepage:
- Size: 351 KB
- Stars: 739
- Watchers: 12
- Forks: 121
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- License: MIT-LICENSE
Awesome Lists containing this project
- awesome-ruby-toolbox - unread - This gem creates a scope for unread objects and adds methods to mark objects as read (Rails Plugins / Rails Activity Feeds)
- awesome-ruby - Unread - Manage read/unread status of ActiveRecord objects - and it's fast. (ORM/ODM Extensions)
README
Unread
======Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
[![Build Status](https://github.com/ledermann/unread/workflows/Test/badge.svg?branch=master)](https://github.com/ledermann/unread/actions)
[![Maintainability](https://api.codeclimate.com/v1/badges/930c8df0f99b20324444/maintainability)](https://codeclimate.com/github/ledermann/unread/maintainability)
[![Coverage Status](https://coveralls.io/repos/ledermann/unread/badge.svg?branch=master)](https://coveralls.io/r/ledermann/unread?branch=master)## Features
* Manages unread records for anything you want readers (e.g. 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 reader
* Needs only one additional database table
* Most important: Great performance## Requirements
* Ruby 3.1 or newer
* Rails 6.1 or newer (including Rails 7.2)
* MySQL, PostgreSQL or SQLite
* Needs a timestamp field in your models (like created_at or updated_at) with a database index on it## Changelog
https://github.com/ledermann/unread/releases
## Installation
Step 1: Add this to your Gemfile:
```ruby
gem 'unread'
```and run
```shell
bundle
```Step 2: Generate and run the migration:
```shell
rails g unread:migration
rake db:migrate
```## Upgrade from previous releases
If you upgrade from an older release of this gem, you should read the [upgrade notes](UPGRADE.md).
## Usage
```ruby
class User < ActiveRecord::Base
acts_as_reader# Optional: Allow a subset of users as readers only
def self.reader_scope
where(is_admin: true)
end
endclass Message < ActiveRecord::Base
acts_as_readable on: :created_at# The `on:` option sets the relevant attribute for comparing timestamps.
#
# The default is :updated_at, so updating a record, which was read by a
# reader makes it unread again.
#
# Using :created_at, only new records will show up as unread. Updating a
# record which was read by a reader, will NOT mark it as unread.
#
# Any other existing timestamp field can be used as `on:` option.
endmessage1 = 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 read messages for a given user
Message.read_by(current_user)
# => [ ]message1.mark_as_read! for: current_user
Message.read_by(current_user)
# => [ message1 ]## 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)
# => trueMessage.mark_as_read! :all, for: current_user
Message.unread_by(current_user)
# => [ ]Message.read_by(current_user)
# => [ message1, message2 ]## Get users that have not read a given message
user1 = User.create!
user2 = User.create!User.have_not_read(message1)
# => [ user1, user2 ]message1.mark_as_read! for: user1
User.have_not_read(message1)
# => [ user2 ]## Get users that have read a given message
User.have_read(message1)
# => [ user1 ]message1.mark_as_read! for: user2
User.have_read(message1)
# => [ user1, user2 ]Message.mark_as_read! :all, for: user1
User.have_not_read(message1)
# => [ ]
User.have_not_read(message2)
# => [ user2 ]User.have_read(message1)
# => [ user1, user2 ]
User.have_read(message2)
# => [ user1 ]## Get all users including their read status for a given message
users = User.with_read_marks_for(message1)
# => [ user1, user2 ]
users[0].have_read?(message1)
# => true
users[1].have_read?(message2)
# => false# Optional: Cleaning up unneeded markers
# Do this in a cron job once a day
Message.cleanup_read_marks!
```## Getting read/unread stats through a relationship
```ruby
class Document < ApplicationRecord
has_many :comments
endclass Comment < ApplicationRecord
acts_as_readable on: :created_at
belongs_to :document
end# Get unread comments count for a document
document = Document.find(1)
default_hash = Hash.new { |h, k| h[k] = { unread: 0, total: 0 } }
document.comments.with_read_marks_for(current_user).reduce(default_hash) do |hash, comment|
hash[comment.id][:unread] += 1 if comment.unread?(current_user)
hash[comment.id][:total] += 1
hash
end
# => {20=>{:unread=>1, :total=>10}, 82=>{:unread=>0, :total=>4}
```Using `with_read_marks_for` here is the key. It uses just one query and makes sure that the following `unread?` invocations use the result of the first query.
## How does it work?
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:
```ruby
# 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)
```Generated query:
```sql
SELECT messages.*
FROM messages
LEFT JOIN read_marks ON read_marks.readable_type = "Message"
AND read_marks.readable_id = messages.id
AND read_marks.reader_id = 42
AND read_marks.reader_type = 'User'
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`.
Copyright (c) 2010-2023 [Georg Ledermann](https://ledermann.dev) and [contributors](https://github.com/ledermann/unread/graphs/contributors), released under the MIT license