All Projects → ledermann → Unread

ledermann / Unread

Licence: mit
Handle unread records and mark them as read with Ruby on Rails

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Unread

Pluck all
A more efficient way to get data from database. Like #pluck method but return array of hashes instead.
Stars: ✭ 83 (-86.99%)
Mutual labels:  activerecord, rails, rubygems
Deep pluck
Allow you to pluck attributes from nested associations without loading a bunch of records.
Stars: ✭ 385 (-39.66%)
Mutual labels:  activerecord, rails, rubygems
Rails or
Cleaner syntax for writing OR Query in Rails 5, 6. And also add #or support to Rails 3 and 4.
Stars: ✭ 86 (-86.52%)
Mutual labels:  activerecord, rails, rubygems
Drafting
Ruby gem for saving drafts of ActiveRecord models
Stars: ✭ 41 (-93.57%)
Mutual labels:  activerecord, rails, rubygems
Pluck to hash
Extend ActiveRecord pluck to return array of hashes
Stars: ✭ 275 (-56.9%)
Mutual labels:  activerecord, rails, rubygems
Isolator
Detect non-atomic interactions within DB transactions
Stars: ✭ 362 (-43.26%)
Mutual labels:  activerecord, rails
Annotate models
Annotate Rails classes with schema and routes info
Stars: ✭ 3,849 (+503.29%)
Mutual labels:  activerecord, rails
Second level cache
Write Through and Read Through caching library inspired by CacheMoney and cache_fu, support ActiveRecord 4, 5 and 6.
Stars: ✭ 380 (-40.44%)
Mutual labels:  activerecord, rails
Strip attributes
🔪 An ActiveModel extension that automatically strips all attributes of leading and trailing whitespace before validation. If the attribute is blank, it strips the value to nil.
Stars: ✭ 441 (-30.88%)
Mutual labels:  activerecord, rails
Ar lazy preload
Lazy loading associations for the ActiveRecord models
Stars: ✭ 281 (-55.96%)
Mutual labels:  activerecord, rails
Store model
Work with JSON-backed attributes as ActiveRecord-ish models
Stars: ✭ 410 (-35.74%)
Mutual labels:  activerecord, rails
Github Ds
A collection of Ruby libraries for working with SQL on top of ActiveRecord's connection
Stars: ✭ 597 (-6.43%)
Mutual labels:  activerecord, rails
Algoliasearch Rails
AlgoliaSearch integration to your favorite ORM
Stars: ✭ 352 (-44.83%)
Mutual labels:  activerecord, rails
Html5 validators
A gem/plugin for Rails 3, Rails 4, Rails 5, and Rails 6 that enables client-side validation using ActiveModel + HTML5 Form Validation
Stars: ✭ 302 (-52.66%)
Mutual labels:  activerecord, rails
Pg party
ActiveRecord PostgreSQL Partitioning
Stars: ✭ 294 (-53.92%)
Mutual labels:  activerecord, rails
Rails Pg Extras
Rails PostgreSQL database performance insights. Locks, index usage, buffer cache hit ratios, vacuum stats and more.
Stars: ✭ 432 (-32.29%)
Mutual labels:  activerecord, rails
Order query
Find next / previous Active Record(s) in one query
Stars: ✭ 472 (-26.02%)
Mutual labels:  activerecord, rails
Attribute normalizer
Adds the ability to normalize attributes cleanly with code blocks and predefined normalizers
Stars: ✭ 473 (-25.86%)
Mutual labels:  activerecord, rails
Zero downtime migrations
Zero downtime migrations with ActiveRecord 3+ and PostgreSQL
Stars: ✭ 513 (-19.59%)
Mutual labels:  activerecord, rails
Elasticsearch Rails
Elasticsearch integrations for ActiveModel/Record and Ruby on Rails
Stars: ✭ 2,896 (+353.92%)
Mutual labels:  activerecord, rails

Unread

Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.

Build Status Maintainability Coverage Status

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 2.5 or newer
  • Rails 4.0 or newer (including Rails 6)
  • 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:

gem 'unread'

and run

bundle

Step 2: Generate and run the migration:

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.

Usage

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
end

class 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.
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 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)
# => true

Message.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

class Document < ApplicationRecord
  has_many :comments
end

class 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:

# 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:

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-2021 Georg Ledermann and contributors, released under the MIT license

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].