module UnreadMongoid module Readable module ClassMethods def mark_as_read!(target, options) user = options[:for] assert_reader(user) if target == :all reset_read_marks_for_user(user) elsif target.is_a?(Array) mark_array_as_read(target, user) else raise ArgumentError end end def mark_array_as_read(array, user) array.each do |obj| raise ArgumentError unless obj.is_a?(self) rm = obj.read_marks.where(user_id: user._id).first || obj.read_marks.build(user_id: user._id) rm.timestamp = obj.send(readable_options[:on]) rm.save! end end # A scope with all items accessable for the given user # It's used in cleanup_read_marks! to support a filtered cleanup # Should be overriden if a user doesn't have access to all items # Default: User has access to all items and should read them all # # Example: # def Message.read_scope(user) # user.visible_messages # end def read_scope(user) self end def cleanup_read_marks! assert_reader_class ReadMark.reader_class.each do |user| if oldest_timestamp = read_scope(user).unread_by(user).sort(readable_options[:on] => :asc).first.send(readable_options[:on]) # There are unread items, so update the global read_mark for this user to the oldest # unread item and delete older read_marks update_read_marks_for_user(user, oldest_timestamp) else # There is no unread item, so deletes all markers and move global timestamp reset_read_marks_for_user(user) end end end def update_read_marks_for_user(user, timestamp) # Delete markers OLDER than the given timestamp user.read_marks.where(:readable_type => self.name).single.older_than(timestamp).delete_all # Change the global timestamp for this user rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.name) rm.timestamp = timestamp - 1.second rm.save! end def reset_read_marks_for_all ReadMark.delete_all :readable_type => self.name ReadMark.reader_class.each do |user| ReadMark.create!(user_id: user._id, readable_type: self.name, timestamp: Time.current.to_s(:db)) end end def reset_read_marks_for_user(user) assert_reader(user) ReadMark.delete_all :readable_type => self.name, :user_id => user._id ReadMark.create! :readable_type => self.name, :user_id => user._id, :timestamp => Time.now end def assert_reader(user) assert_reader_class unless user.is_a?(ReadMark.reader_class) raise ArgumentError, "Class #{user.class.name} is not registered by acts_as_reader!" end unless user._id raise ArgumentError, "The given user has no id!" end end def assert_reader_class raise RuntimeError, 'There is no class using acts_as_reader!' unless ReadMark.reader_class end end module InstanceMethods def unread?(user) self.class.unread_by(user).and(_id: self._id).exists? end def mark_as_read!(options) user = options[:for] self.class.assert_reader(user) if unread?(user) rm = read_mark(user) || read_marks.build(:user_id => user._id) rm.timestamp = self.send(readable_options[:on]).to_s(:db) rm.save! end end def read_mark(user) read_marks.where(:user_id => user._id).first end end end end