unread 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -7
- data/UPGRADE.md +49 -0
- data/bin/console +30 -0
- data/lib/generators/unread/migration/templates/migration.rb +2 -2
- data/lib/generators/unread/polymorphic_reader_migration/polymorphic_reader_migration_generator.rb +23 -0
- data/lib/generators/unread/polymorphic_reader_migration/templates/unread_polymorphic_reader_migration.rb +16 -0
- data/lib/unread.rb +4 -1
- data/lib/unread/base.rb +9 -14
- data/lib/unread/garbage_collector.rb +54 -0
- data/lib/unread/read_mark.rb +3 -8
- data/lib/unread/readable.rb +43 -80
- data/lib/unread/readable_scopes.rb +14 -12
- data/lib/unread/reader.rb +10 -1
- data/lib/unread/reader_scopes.rb +8 -1
- data/lib/unread/version.rb +1 -1
- data/spec/app/models/different_reader.rb +5 -0
- data/spec/{model → app/models}/email.rb +0 -0
- data/spec/{model → app/models}/reader.rb +5 -1
- data/spec/app/models/sti_reader.rb +6 -0
- data/spec/spec_helper.rb +10 -20
- data/spec/support/spec_migration.rb +23 -0
- data/spec/{base_spec.rb → unread/base_spec.rb} +1 -1
- data/spec/unread/garbage_collector_spec.rb +52 -0
- data/spec/{read_mark_spec.rb → unread/read_mark_spec.rb} +1 -5
- data/spec/{readable_spec.rb → unread/readable_spec.rb} +4 -44
- data/spec/unread/reader_scopes_spec.rb +9 -0
- data/spec/{reader_spec.rb → unread/reader_spec.rb} +20 -0
- metadata +32 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a7a52ce94c2d2bf78f926c90869494d43c7c0d5
|
4
|
+
data.tar.gz: 3f75e1d043b89b5ec6cee60f5b1197106399df23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 576527e97d9c23f90a6d96061598e890a86c3fcb06a36030472e4e12f04a5abfe231eea980c6a122335319f9630c9c71bb4604e77f01018a7b378046dda736ae
|
7
|
+
data.tar.gz: 34fd6607a8935bf86ca508def361545f0d1a56d6b9c5057102db317dca29769d27a47d98b8059750d6063bb74a6adfee348db47c18453848ed859f4c1e3f2a83
|
data/README.md
CHANGED
@@ -9,10 +9,10 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
|
9
9
|
|
10
10
|
## Features
|
11
11
|
|
12
|
-
* Manages unread records for anything you want users to read (like messages, documents, comments etc.)
|
12
|
+
* Manages unread records for anything you want readers (e.g. users) to read (like messages, documents, comments etc.)
|
13
13
|
* Supports _mark as read_ to mark a **single** record as read
|
14
14
|
* Supports _mark all as read_ to mark **all** records as read in a single step
|
15
|
-
* Gives you a scope to get the unread records for a given
|
15
|
+
* Gives you a scope to get the unread records for a given reader
|
16
16
|
* Needs only one additional database table
|
17
17
|
* Most important: Great performance
|
18
18
|
|
@@ -51,6 +51,10 @@ rails g unread:migration
|
|
51
51
|
rake db:migrate
|
52
52
|
```
|
53
53
|
|
54
|
+
## Upgrade from previous releases
|
55
|
+
|
56
|
+
If you upgrade from an older release of this gem, you should read the [upgrade notes](UPGRADE.md).
|
57
|
+
|
54
58
|
|
55
59
|
## Usage
|
56
60
|
|
@@ -58,9 +62,10 @@ rake db:migrate
|
|
58
62
|
class User < ActiveRecord::Base
|
59
63
|
acts_as_reader
|
60
64
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
65
|
+
# Optional: Allow a subset of users as readers only
|
66
|
+
def self.reader_scope
|
67
|
+
where(:is_admin => true)
|
68
|
+
end
|
64
69
|
end
|
65
70
|
|
66
71
|
class Message < ActiveRecord::Base
|
@@ -173,7 +178,8 @@ SELECT messages.*
|
|
173
178
|
FROM messages
|
174
179
|
LEFT JOIN read_marks ON read_marks.readable_type = "Message"
|
175
180
|
AND read_marks.readable_id = messages.id
|
176
|
-
AND read_marks.
|
181
|
+
AND read_marks.reader_id = 42
|
182
|
+
AND read_marks.reader_type = 'User'
|
177
183
|
AND read_marks.timestamp >= messages.created_at
|
178
184
|
WHERE read_marks.id IS NULL
|
179
185
|
AND messages.created_at > '2010-10-20 08:50:00'
|
@@ -182,4 +188,4 @@ AND messages.created_at > '2010-10-20 08:50:00'
|
|
182
188
|
Hint: You should add a database index on `messages.created_at`.
|
183
189
|
|
184
190
|
|
185
|
-
Copyright (c) 2010-2015 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
|
191
|
+
Copyright (c) 2010-2015 [Georg Ledermann](http://www.georg-ledermann.de) and [contributors](https://github.com/ledermann/unread/graphs/contributors), released under the MIT license
|
data/UPGRADE.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Upgrade notes
|
2
|
+
|
3
|
+
## Breaking changes with v0.7.0
|
4
|
+
|
5
|
+
There are two important changes needing your attention. Please read the following hints carefully!
|
6
|
+
|
7
|
+
|
8
|
+
### Polymorphic readers
|
9
|
+
|
10
|
+
The gem accepts any type of classes as reader and it's not limited to `User` class anymore. So you can do stuff like:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
Customer.have_not_read(message1)
|
14
|
+
message1.mark_as_read! :for => Customer.find(1)
|
15
|
+
```
|
16
|
+
|
17
|
+
If you are upgrading from v0.6.3 or older, you need to do the following after upgrading:
|
18
|
+
|
19
|
+
```shell
|
20
|
+
rails g unread:polymorphic_reader_migration
|
21
|
+
rake db:migrate
|
22
|
+
```
|
23
|
+
|
24
|
+
This will alter the `read_marks` table to replace `user` association to a polymorphic association named `reader`. Therefore, `user_id` is going to be renamed to `reader_id` and `reader_type` is going to be added.
|
25
|
+
|
26
|
+
This change should not break your code unless you've worked with `ReadMark` model directly.
|
27
|
+
|
28
|
+
|
29
|
+
### Defining reader_scope
|
30
|
+
|
31
|
+
The class method `acts_as_reader` doesn't take the option `:scope` anymore. If you have used it, please change this ...
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class User < ActiveRecord::Base
|
35
|
+
acts_as_reader :scope => -> { where(:is_admin => true) }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
... to this
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
acts_as_reader
|
44
|
+
|
45
|
+
def self.reader_scope
|
46
|
+
where(:is_admin => true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
data/bin/console
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'unread'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.expand_path('..', File.dirname(__FILE__)))
|
7
|
+
require 'lib/generators/unread/migration/templates/migration.rb'
|
8
|
+
require 'spec/support/spec_migration.rb'
|
9
|
+
|
10
|
+
require 'spec/app/models/reader'
|
11
|
+
require 'spec/app/models/different_reader'
|
12
|
+
require 'spec/app/models/sti_reader'
|
13
|
+
require 'spec/app/models/email'
|
14
|
+
|
15
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
16
|
+
# with your gem easier. You can also use a different console, if you like.
|
17
|
+
|
18
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
19
|
+
# require "pry"
|
20
|
+
# Pry.start
|
21
|
+
|
22
|
+
ActiveRecord::Base.configurations = YAML.load_file('spec/database.yml')
|
23
|
+
ActiveRecord::Base.establish_connection(:sqlite)
|
24
|
+
ActiveRecord::Migration.verbose = false
|
25
|
+
|
26
|
+
UnreadMigration.up
|
27
|
+
SpecMigration.up
|
28
|
+
|
29
|
+
require 'irb'
|
30
|
+
IRB.start
|
@@ -2,11 +2,11 @@ class UnreadMigration < ActiveRecord::Migration
|
|
2
2
|
def self.up
|
3
3
|
create_table :read_marks, force: true do |t|
|
4
4
|
t.references :readable, polymorphic: { null: false }
|
5
|
-
t.references :
|
5
|
+
t.references :reader, polymorphic: { null: false }
|
6
6
|
t.datetime :timestamp
|
7
7
|
end
|
8
8
|
|
9
|
-
add_index :read_marks, [:
|
9
|
+
add_index :read_marks, [:reader_id, :reader_type, :readable_type, :readable_id], name: 'read_marks_reader_readable_index'
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.down
|
data/lib/generators/unread/polymorphic_reader_migration/polymorphic_reader_migration_generator.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Unread
|
5
|
+
class PolymorphicReaderMigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "Generates update migration to make reader of read_markers polymorphic"
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
|
11
|
+
def create_migration_file
|
12
|
+
migration_template 'unread_polymorphic_reader_migration.rb', 'db/migrate/unread_polymorphic_reader_migration.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.next_migration_number(dirname)
|
16
|
+
if ActiveRecord::Base.timestamped_migrations
|
17
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
18
|
+
else
|
19
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class UnreadPolymorphicReaderMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
remove_index :read_marks, [:user_id, :readable_type, :readable_id]
|
4
|
+
rename_column :read_marks, :user_id, :reader_id
|
5
|
+
add_column :read_marks, :reader_type, :string
|
6
|
+
execute "update read_marks set reader_type = 'User'"
|
7
|
+
add_index :read_marks, [:reader_id, :reader_type, :readable_type, :readable_id], name: 'read_marks_reader_readable_index'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.down
|
11
|
+
remove_index :read_marks, name: 'read_marks_reader_readable_index'
|
12
|
+
remove_column :read_marks, :reader_type
|
13
|
+
rename_column :read_marks, :reader_id, :user_id
|
14
|
+
add_index :read_marks, [:user_id, :readable_type, :readable_id]
|
15
|
+
end
|
16
|
+
end
|
data/lib/unread.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
1
3
|
require 'unread/base'
|
2
4
|
require 'unread/read_mark'
|
3
5
|
require 'unread/readable'
|
4
6
|
require 'unread/reader'
|
5
7
|
require 'unread/readable_scopes'
|
6
8
|
require 'unread/reader_scopes'
|
9
|
+
require 'unread/garbage_collector'
|
7
10
|
require 'unread/version'
|
8
11
|
|
9
|
-
ActiveRecord::Base.send :include, Unread
|
12
|
+
ActiveRecord::Base.send :include, Unread
|
data/lib/unread/base.rb
CHANGED
@@ -4,22 +4,17 @@ module Unread
|
|
4
4
|
end
|
5
5
|
|
6
6
|
module Base
|
7
|
-
def acts_as_reader
|
8
|
-
ReadMark.
|
9
|
-
|
10
|
-
has_many :read_marks, :dependent => :delete_all, :foreign_key => 'user_id', :inverse_of => :user
|
11
|
-
|
12
|
-
after_create do |user|
|
13
|
-
# We assume that a new user should not be tackled by tons of old messages
|
14
|
-
# created BEFORE he signed up.
|
15
|
-
# Instead, the new user starts with zero unread messages
|
16
|
-
(ReadMark.readable_classes || []).each do |klass|
|
17
|
-
klass.mark_as_read! :all, :for => user
|
18
|
-
end
|
7
|
+
def acts_as_reader
|
8
|
+
unless ReadMark.reflections.include?(:reader)
|
9
|
+
ReadMark.belongs_to :reader, :polymorphic => true, inverse_of: :read_marks
|
19
10
|
end
|
20
11
|
|
21
|
-
|
22
|
-
|
12
|
+
has_many :read_marks, :dependent => :delete_all, as: :reader, :inverse_of => :reader
|
13
|
+
|
14
|
+
after_create :setup_new_reader
|
15
|
+
|
16
|
+
ReadMark.reader_classes ||= []
|
17
|
+
ReadMark.reader_classes << self
|
23
18
|
|
24
19
|
include Reader::InstanceMethods
|
25
20
|
extend Reader::ClassMethods
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Unread
|
2
|
+
class GarbageCollector
|
3
|
+
def initialize(readable_class)
|
4
|
+
@readable_class = readable_class
|
5
|
+
end
|
6
|
+
attr_reader :readable_class
|
7
|
+
|
8
|
+
def run!
|
9
|
+
ReadMark.reader_classes.each do |reader_class|
|
10
|
+
readers_to_cleanup(reader_class).each do |reader|
|
11
|
+
if oldest_timestamp = readable_class.read_scope(reader).
|
12
|
+
unread_by(reader).
|
13
|
+
minimum(readable_class.readable_options[:on])
|
14
|
+
# There are unread items, so update the global read_mark for this reader to the oldest
|
15
|
+
# unread item and delete older read_marks
|
16
|
+
update_read_marks_for_user(reader, oldest_timestamp)
|
17
|
+
else
|
18
|
+
# There is no unread item, so deletes all markers and move global timestamp
|
19
|
+
readable_class.reset_read_marks_for_user(reader)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# Not for every reader a cleanup is needed.
|
27
|
+
# Look for those readers with at least one single read mark
|
28
|
+
def readers_to_cleanup(reader_class)
|
29
|
+
reader_class.
|
30
|
+
reader_scope.
|
31
|
+
joins(:read_marks).
|
32
|
+
where(:read_marks => { :readable_type => readable_class.base_class.name }).
|
33
|
+
group('read_marks.reader_type, read_marks.reader_id').
|
34
|
+
having('COUNT(read_marks.id) > 1')
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_read_marks_for_user(reader, timestamp)
|
38
|
+
ReadMark.transaction do
|
39
|
+
# Delete markers OLDER than the given timestamp
|
40
|
+
reader.read_marks.
|
41
|
+
where(:readable_type => readable_class.base_class.name).
|
42
|
+
single.
|
43
|
+
older_than(timestamp).
|
44
|
+
delete_all
|
45
|
+
|
46
|
+
# Change the global timestamp for this reader
|
47
|
+
rm = reader.read_mark_global(readable_class) || reader.read_marks.build
|
48
|
+
rm.readable_type = readable_class.base_class.name
|
49
|
+
rm.timestamp = timestamp - 1.second
|
50
|
+
rm.save!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/unread/read_mark.rb
CHANGED
@@ -1,20 +1,15 @@
|
|
1
1
|
class ReadMark < ActiveRecord::Base
|
2
2
|
belongs_to :readable, :polymorphic => true
|
3
3
|
|
4
|
-
validates_presence_of :
|
4
|
+
validates_presence_of :reader_id, :reader_type, :readable_type
|
5
5
|
|
6
6
|
scope :global, lambda { where(:readable_id => nil) }
|
7
7
|
scope :single, lambda { where('readable_id IS NOT NULL') }
|
8
8
|
scope :older_than, lambda { |timestamp| where([ 'timestamp < ?', timestamp ]) }
|
9
9
|
|
10
|
-
# Returns the
|
11
|
-
class_attribute :
|
12
|
-
class_attribute :reader_options
|
10
|
+
# Returns the classes defined by acts_as_reader
|
11
|
+
class_attribute :reader_classes
|
13
12
|
|
14
13
|
# Returns the classes defined by acts_as_readable
|
15
14
|
class_attribute :readable_classes
|
16
|
-
|
17
|
-
def self.reader_scope
|
18
|
-
reader_options[:scope].try(:call) || reader_class
|
19
|
-
end
|
20
15
|
end
|
data/lib/unread/readable.rb
CHANGED
@@ -4,21 +4,21 @@ module Unread
|
|
4
4
|
def mark_as_read!(target, options)
|
5
5
|
raise ArgumentError unless options.is_a?(Hash)
|
6
6
|
|
7
|
-
|
8
|
-
assert_reader(
|
7
|
+
reader = options[:for]
|
8
|
+
assert_reader(reader)
|
9
9
|
|
10
10
|
if target == :all
|
11
|
-
reset_read_marks_for_user(
|
11
|
+
reset_read_marks_for_user(reader)
|
12
12
|
elsif target.is_a?(Array)
|
13
|
-
mark_array_as_read(target,
|
13
|
+
mark_array_as_read(target, reader)
|
14
14
|
else
|
15
15
|
raise ArgumentError
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def mark_array_as_read(array,
|
19
|
+
def mark_array_as_read(array, reader)
|
20
20
|
ReadMark.transaction do
|
21
|
-
global_timestamp =
|
21
|
+
global_timestamp = reader.read_mark_global(self).try(:timestamp)
|
22
22
|
|
23
23
|
array.each do |obj|
|
24
24
|
raise ArgumentError unless obj.is_a?(self)
|
@@ -27,140 +27,103 @@ module Unread
|
|
27
27
|
if global_timestamp && global_timestamp >= timestamp
|
28
28
|
# The object is implicitly marked as read, so there is nothing to do
|
29
29
|
else
|
30
|
-
rm = obj.read_marks.where(:
|
31
|
-
rm.
|
32
|
-
rm.
|
30
|
+
rm = obj.read_marks.where(:reader_id => reader.id, :reader_type => reader.class.base_class.name).first || obj.read_marks.build
|
31
|
+
rm.reader_id = reader.id
|
32
|
+
rm.reader_type = reader.class.base_class.name
|
33
|
+
rm.timestamp = timestamp
|
33
34
|
rm.save!
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
# A scope with all items accessable for the given
|
40
|
+
# A scope with all items accessable for the given reader
|
40
41
|
# It's used in cleanup_read_marks! to support a filtered cleanup
|
41
|
-
# Should be overriden if a
|
42
|
-
# Default:
|
42
|
+
# Should be overriden if a reader doesn't have access to all items
|
43
|
+
# Default: reader has access to all items and should read them all
|
43
44
|
#
|
44
45
|
# Example:
|
45
|
-
# def Message.read_scope(
|
46
|
-
#
|
46
|
+
# def Message.read_scope(reader)
|
47
|
+
# reader.visible_messages
|
47
48
|
# end
|
48
|
-
def read_scope(
|
49
|
+
def read_scope(reader)
|
49
50
|
self
|
50
51
|
end
|
51
52
|
|
52
53
|
def cleanup_read_marks!
|
53
54
|
assert_reader_class
|
54
|
-
|
55
|
-
ReadMark.reader_scope.find_each do |user|
|
56
|
-
if oldest_timestamp = read_scope(user).unread_by(user).minimum(readable_options[:on])
|
57
|
-
# There are unread items, so update the global read_mark for this user to the oldest
|
58
|
-
# unread item and delete older read_marks
|
59
|
-
update_read_marks_for_user(user, oldest_timestamp)
|
60
|
-
else
|
61
|
-
# There is no unread item, so deletes all markers and move global timestamp
|
62
|
-
reset_read_marks_for_user(user)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def update_read_marks_for_user(user, timestamp)
|
68
|
-
ReadMark.transaction do
|
69
|
-
# Delete markers OLDER than the given timestamp
|
70
|
-
user.read_marks.where(:readable_type => self.base_class.name).single.older_than(timestamp).delete_all
|
71
|
-
|
72
|
-
# Change the global timestamp for this user
|
73
|
-
rm = user.read_mark_global(self) || user.read_marks.build
|
74
|
-
rm.readable_type = self.base_class.name
|
75
|
-
rm.timestamp = timestamp - 1.second
|
76
|
-
rm.save!
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def reset_read_marks_for_all
|
81
|
-
ReadMark.transaction do
|
82
|
-
ReadMark.delete_all :readable_type => self.base_class.name
|
83
|
-
|
84
|
-
# Build a SELECT statement with all relevant readers
|
85
|
-
reader_sql = ReadMark.
|
86
|
-
reader_scope.
|
87
|
-
select("#{ReadMark.reader_scope.quoted_table_name}.#{ReadMark.reader_scope.quoted_primary_key},
|
88
|
-
'#{self.base_class.name}',
|
89
|
-
'#{connection.quoted_date Time.current}'").to_sql
|
90
|
-
|
91
|
-
ReadMark.connection.execute <<-EOT
|
92
|
-
INSERT INTO read_marks (user_id, readable_type, timestamp)
|
93
|
-
#{reader_sql}
|
94
|
-
EOT
|
95
|
-
end
|
55
|
+
Unread::GarbageCollector.new(self).run!
|
96
56
|
end
|
97
57
|
|
98
|
-
def reset_read_marks_for_user(
|
99
|
-
assert_reader(
|
58
|
+
def reset_read_marks_for_user(reader)
|
59
|
+
assert_reader(reader)
|
100
60
|
|
101
61
|
ReadMark.transaction do
|
102
|
-
ReadMark.delete_all :readable_type => self.base_class.name, :
|
62
|
+
ReadMark.delete_all :readable_type => self.base_class.name, :reader_id => reader.id, :reader_type => reader.class.base_class.name
|
103
63
|
|
104
64
|
ReadMark.create! do |rm|
|
105
65
|
rm.readable_type = self.base_class.name
|
106
|
-
rm.
|
66
|
+
rm.reader_id = reader.id
|
67
|
+
rm.reader_type = reader.class.base_class.name
|
107
68
|
rm.timestamp = Time.current
|
108
69
|
end
|
109
70
|
end
|
110
71
|
|
111
|
-
|
72
|
+
reader.forget_memoized_read_mark_global
|
112
73
|
end
|
113
74
|
|
114
|
-
def assert_reader(
|
75
|
+
def assert_reader(reader)
|
115
76
|
assert_reader_class
|
116
77
|
|
117
|
-
raise ArgumentError, "Class #{
|
118
|
-
raise ArgumentError, "The given
|
78
|
+
raise ArgumentError, "Class #{reader.class.name} is not registered by acts_as_reader." unless ReadMark.reader_classes.any? { |klass| reader.is_a?(klass) }
|
79
|
+
raise ArgumentError, "The given reader has no id." unless reader.id
|
119
80
|
end
|
120
81
|
|
121
82
|
def assert_reader_class
|
122
|
-
raise RuntimeError, 'There is no class using acts_as_reader.' unless ReadMark.
|
83
|
+
raise RuntimeError, 'There is no class using acts_as_reader.' unless ReadMark.reader_classes
|
123
84
|
end
|
124
85
|
end
|
125
86
|
|
126
87
|
module InstanceMethods
|
127
|
-
def unread?(
|
128
|
-
if self.respond_to?(:read_mark_id) && read_mark_id_belongs_to?(
|
88
|
+
def unread?(reader)
|
89
|
+
if self.respond_to?(:read_mark_id) && read_mark_id_belongs_to?(reader)
|
129
90
|
# For use with scope "with_read_marks_for"
|
130
91
|
return false if self.read_mark_id
|
131
92
|
|
132
|
-
if global_timestamp =
|
93
|
+
if global_timestamp = reader.read_mark_global(self.class).try(:timestamp)
|
133
94
|
self.send(readable_options[:on]) > global_timestamp
|
134
95
|
else
|
135
96
|
true
|
136
97
|
end
|
137
98
|
else
|
138
|
-
self.class.unread_by(
|
99
|
+
self.class.unread_by(reader).exists?(self.id)
|
139
100
|
end
|
140
101
|
end
|
141
102
|
|
142
103
|
def mark_as_read!(options)
|
143
|
-
|
144
|
-
self.class.assert_reader(
|
104
|
+
reader = options[:for]
|
105
|
+
self.class.assert_reader(reader)
|
145
106
|
|
146
107
|
ReadMark.transaction do
|
147
|
-
if unread?(
|
148
|
-
rm = read_mark(
|
149
|
-
rm.
|
150
|
-
rm.
|
108
|
+
if unread?(reader)
|
109
|
+
rm = read_mark(reader) || read_marks.build
|
110
|
+
rm.reader_id = reader.id
|
111
|
+
rm.reader_type = reader.class.base_class.name
|
112
|
+
rm.timestamp = self.send(readable_options[:on])
|
151
113
|
rm.save!
|
152
114
|
end
|
153
115
|
end
|
154
116
|
end
|
155
117
|
|
156
|
-
def read_mark(
|
157
|
-
read_marks.where(:
|
118
|
+
def read_mark(reader)
|
119
|
+
read_marks.where(:reader_id => reader.id, reader_type: reader.class.base_class.name).first
|
158
120
|
end
|
159
121
|
|
160
122
|
private
|
161
123
|
|
162
|
-
def read_mark_id_belongs_to?(
|
163
|
-
self.
|
124
|
+
def read_mark_id_belongs_to?(reader)
|
125
|
+
self.read_mark_reader_id == reader.id &&
|
126
|
+
self.read_mark_reader_type == reader.class.base_class.name
|
164
127
|
end
|
165
128
|
end
|
166
129
|
end
|
@@ -1,20 +1,21 @@
|
|
1
1
|
module Unread
|
2
2
|
module Readable
|
3
3
|
module Scopes
|
4
|
-
def join_read_marks(
|
5
|
-
assert_reader(
|
4
|
+
def join_read_marks(reader)
|
5
|
+
assert_reader(reader)
|
6
6
|
|
7
7
|
joins "LEFT JOIN read_marks
|
8
8
|
ON read_marks.readable_type = '#{base_class.name}'
|
9
9
|
AND read_marks.readable_id = #{quoted_table_name}.#{quoted_primary_key}
|
10
|
-
AND read_marks.
|
10
|
+
AND read_marks.reader_id = #{quote_bound_value(reader.id)}
|
11
|
+
AND read_marks.reader_type = #{quote_bound_value(reader.class.base_class.name)}
|
11
12
|
AND read_marks.timestamp >= #{quoted_table_name}.#{connection.quote_column_name(readable_options[:on])}"
|
12
13
|
end
|
13
14
|
|
14
|
-
def unread_by(
|
15
|
-
result = join_read_marks(
|
15
|
+
def unread_by(reader)
|
16
|
+
result = join_read_marks(reader)
|
16
17
|
|
17
|
-
if global_time_stamp =
|
18
|
+
if global_time_stamp = reader.read_mark_global(self).try(:timestamp)
|
18
19
|
result.where("read_marks.id IS NULL
|
19
20
|
AND #{quoted_table_name}.#{connection.quote_column_name(readable_options[:on])} > ?", global_time_stamp)
|
20
21
|
else
|
@@ -22,10 +23,10 @@ module Unread
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def read_by(
|
26
|
-
result = join_read_marks(
|
26
|
+
def read_by(reader)
|
27
|
+
result = join_read_marks(reader)
|
27
28
|
|
28
|
-
if global_time_stamp =
|
29
|
+
if global_time_stamp = reader.read_mark_global(self).try(:timestamp)
|
29
30
|
result.where("read_marks.id IS NOT NULL
|
30
31
|
OR #{quoted_table_name}.#{connection.quote_column_name(readable_options[:on])} <= ?", global_time_stamp)
|
31
32
|
else
|
@@ -33,10 +34,11 @@ module Unread
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
def with_read_marks_for(
|
37
|
-
join_read_marks(
|
37
|
+
def with_read_marks_for(reader)
|
38
|
+
join_read_marks(reader).select("#{quoted_table_name}.*,
|
38
39
|
read_marks.id AS read_mark_id,
|
39
|
-
#{quote_bound_value(
|
40
|
+
#{quote_bound_value(reader.class.base_class.name)} AS read_mark_reader_type,
|
41
|
+
#{quote_bound_value(reader.id)} AS read_mark_reader_id")
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
data/lib/unread/reader.rb
CHANGED
@@ -37,6 +37,15 @@ module Unread
|
|
37
37
|
self.read_mark_readable_type == readable.class.base_class.name &&
|
38
38
|
(self.read_mark_readable_id.nil? || self.read_mark_readable_id == readable.id)
|
39
39
|
end
|
40
|
+
|
41
|
+
# We assume that a new reader should not be tackled by tons of old messages created BEFORE he signed up.
|
42
|
+
# Instead, the new reader should start with zero unread messages.
|
43
|
+
# If you don't want this, you can override this method in your reader class
|
44
|
+
def setup_new_reader
|
45
|
+
(ReadMark.readable_classes || []).each do |klass|
|
46
|
+
klass.mark_as_read! :all, :for => self
|
47
|
+
end
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end
|
42
|
-
end
|
51
|
+
end
|
data/lib/unread/reader_scopes.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
module Unread
|
2
2
|
module Reader
|
3
3
|
module Scopes
|
4
|
+
# This class method may be overriden to restrict readers to a subset of records
|
5
|
+
# It must return self or a ActiveRecord::Relation
|
6
|
+
def reader_scope
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
4
10
|
def join_read_marks(readable)
|
5
11
|
assert_readable(readable)
|
6
12
|
|
7
13
|
joins "LEFT JOIN read_marks
|
8
14
|
ON read_marks.readable_type = '#{readable.class.base_class.name}'
|
9
15
|
AND (read_marks.readable_id = #{readable.id} OR read_marks.readable_id IS NULL)
|
10
|
-
AND read_marks.
|
16
|
+
AND read_marks.reader_id = #{quoted_table_name}.#{quoted_primary_key}
|
17
|
+
AND read_marks.reader_type = '#{connection.quote_string base_class.name}'
|
11
18
|
AND read_marks.timestamp >= '#{connection.quoted_date readable.send(readable.class.readable_options[:on])}'"
|
12
19
|
end
|
13
20
|
|
data/lib/unread/version.rb
CHANGED
File without changes
|
data/spec/spec_helper.rb
CHANGED
@@ -9,12 +9,14 @@ SimpleCov.start do
|
|
9
9
|
add_filter '/spec/'
|
10
10
|
end
|
11
11
|
|
12
|
-
require 'active_record'
|
13
12
|
require 'timecop'
|
14
13
|
require 'unread'
|
14
|
+
require 'generators/unread/migration/templates/migration.rb'
|
15
15
|
|
16
|
-
require '
|
17
|
-
require '
|
16
|
+
require 'app/models/reader'
|
17
|
+
require 'app/models/different_reader'
|
18
|
+
require 'app/models/sti_reader'
|
19
|
+
require 'app/models/email'
|
18
20
|
|
19
21
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
20
22
|
# in spec/support/ and its subdirectories.
|
@@ -42,7 +44,7 @@ RSpec.configure do |config|
|
|
42
44
|
end
|
43
45
|
|
44
46
|
config.after :suite do
|
45
|
-
UnreadMigration.
|
47
|
+
UnreadMigration.down
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -62,26 +64,14 @@ def setup_db
|
|
62
64
|
ActiveRecord::Base.default_timezone = :utc
|
63
65
|
ActiveRecord::Migration.verbose = false
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
ActiveRecord::Schema.define(:version => 1) do
|
69
|
-
create_table :readers, :primary_key => 'number', :force => true do |t|
|
70
|
-
t.string :name
|
71
|
-
end
|
72
|
-
|
73
|
-
create_table :documents, :primary_key => 'uid', :force => true do |t|
|
74
|
-
t.string :type
|
75
|
-
t.string :subject
|
76
|
-
t.text :content
|
77
|
-
t.datetime :created_at
|
78
|
-
t.datetime :updated_at
|
79
|
-
end
|
80
|
-
end
|
67
|
+
UnreadMigration.up
|
68
|
+
SpecMigration.up
|
81
69
|
end
|
82
70
|
|
83
71
|
def clear_db
|
84
72
|
Reader.delete_all
|
73
|
+
DifferentReader.delete_all
|
74
|
+
StiReader.delete_all
|
85
75
|
Email.delete_all
|
86
76
|
ReadMark.delete_all
|
87
77
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class SpecMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :readers, :primary_key => 'number', :force => true do |t|
|
4
|
+
t.string :name
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table :different_readers, :primary_key => 'number', :force => true do |t|
|
8
|
+
t.string :name
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :customers, :force => true do |t|
|
12
|
+
t.string :type
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :documents, :primary_key => 'uid', :force => true do |t|
|
16
|
+
t.string :type
|
17
|
+
t.string :subject
|
18
|
+
t.text :content
|
19
|
+
t.datetime :created_at
|
20
|
+
t.datetime :updated_at
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -14,7 +14,7 @@ describe Unread::Base do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should define association for ReadMark" do
|
17
|
-
expect(@reader.read_marks.first.
|
17
|
+
expect(@reader.read_marks.first.reader).to eq(@reader)
|
18
18
|
end
|
19
19
|
|
20
20
|
it "should reset read_marks for created reader" do
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Unread::GarbageCollector do
|
4
|
+
before :each do
|
5
|
+
@reader = Reader.create! :name => 'David'
|
6
|
+
@other_reader = Reader.create :name => 'Matz'
|
7
|
+
@sti_reader = StiReader.create!
|
8
|
+
wait
|
9
|
+
@email1 = Email.create!
|
10
|
+
wait
|
11
|
+
@email2 = Email.create!
|
12
|
+
end
|
13
|
+
|
14
|
+
describe :run! do
|
15
|
+
it "should delete all single read marks" do
|
16
|
+
expect(@reader.read_marks.single.count).to eq 0
|
17
|
+
|
18
|
+
@email1.mark_as_read! :for => @reader
|
19
|
+
|
20
|
+
expect(Email.unread_by(@reader)).to eq [@email2]
|
21
|
+
expect(@reader.read_marks.single.count).to eq 1
|
22
|
+
|
23
|
+
Unread::GarbageCollector.new(Email).run!
|
24
|
+
|
25
|
+
@reader.reload
|
26
|
+
expect(@reader.read_marks.single.count).to eq 0
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should reset if all objects are read" do
|
30
|
+
@email1.mark_as_read! :for => @reader
|
31
|
+
@email2.mark_as_read! :for => @reader
|
32
|
+
|
33
|
+
expect(@reader.read_marks.single.count).to eq 2
|
34
|
+
|
35
|
+
Unread::GarbageCollector.new(Email).run!
|
36
|
+
|
37
|
+
expect(@reader.read_marks.single.count).to eq 0
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not delete read marks from other readables" do
|
41
|
+
other_read_mark = @reader.read_marks.create! do |rm|
|
42
|
+
rm.readable_type = 'Foo'
|
43
|
+
rm.readable_id = 42
|
44
|
+
rm.timestamp = 5.years.ago
|
45
|
+
end
|
46
|
+
|
47
|
+
Unread::GarbageCollector.new(Email).run!
|
48
|
+
|
49
|
+
expect(ReadMark.exists?(other_read_mark.id)).to be_truthy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -2,11 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe ReadMark do
|
4
4
|
it "should have reader_class" do
|
5
|
-
expect(ReadMark.
|
6
|
-
end
|
7
|
-
|
8
|
-
it "should have reader_scope" do
|
9
|
-
expect(ReadMark.reader_scope).to eq Reader.not_foo.not_bar
|
5
|
+
expect(ReadMark.reader_classes).to eq [Reader, DifferentReader, StiReader]
|
10
6
|
end
|
11
7
|
|
12
8
|
it "should have readable_classes" do
|
@@ -4,6 +4,7 @@ describe Unread::Readable do
|
|
4
4
|
before :each do
|
5
5
|
@reader = Reader.create! :name => 'David'
|
6
6
|
@other_reader = Reader.create :name => 'Matz'
|
7
|
+
@sti_reader = StiReader.create!
|
7
8
|
wait
|
8
9
|
@email1 = Email.create!
|
9
10
|
wait
|
@@ -279,7 +280,7 @@ describe Unread::Readable do
|
|
279
280
|
expect(@reader.read_mark_global(Email).timestamp).to eq Time.current
|
280
281
|
expect(@reader.read_marks.single).to eq []
|
281
282
|
expect(ReadMark.single.count).to eq 0
|
282
|
-
expect(ReadMark.global.count).to eq
|
283
|
+
expect(ReadMark.global.count).to eq 3
|
283
284
|
end
|
284
285
|
|
285
286
|
it "should mark all objects as read with existing read objects" do
|
@@ -309,51 +310,10 @@ describe Unread::Readable do
|
|
309
310
|
end
|
310
311
|
end
|
311
312
|
|
312
|
-
describe :reset_read_marks_for_all do
|
313
|
-
it "should reset read marks" do
|
314
|
-
Email.reset_read_marks_for_all
|
315
|
-
|
316
|
-
expect(ReadMark.single.count).to eq 0
|
317
|
-
expect(ReadMark.global.count).to eq 2
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
313
|
describe :cleanup_read_marks! do
|
322
|
-
it "should
|
323
|
-
expect(
|
324
|
-
|
325
|
-
@email1.mark_as_read! :for => @reader
|
326
|
-
|
327
|
-
expect(Email.unread_by(@reader)).to eq [@email2]
|
328
|
-
expect(@reader.read_marks.single.count).to eq 1
|
329
|
-
|
314
|
+
it "should run garbage collector" do
|
315
|
+
expect(Unread::GarbageCollector).to receive(:new).with(Email).and_return(double :run! => true)
|
330
316
|
Email.cleanup_read_marks!
|
331
|
-
|
332
|
-
@reader.reload
|
333
|
-
expect(@reader.read_marks.single.count).to eq 0
|
334
|
-
end
|
335
|
-
|
336
|
-
it "should reset if all objects are read" do
|
337
|
-
@email1.mark_as_read! :for => @reader
|
338
|
-
@email2.mark_as_read! :for => @reader
|
339
|
-
|
340
|
-
expect(@reader.read_marks.single.count).to eq 2
|
341
|
-
|
342
|
-
Email.cleanup_read_marks!
|
343
|
-
|
344
|
-
expect(@reader.read_marks.single.count).to eq 0
|
345
|
-
end
|
346
|
-
|
347
|
-
it "should not delete read marks from other readables" do
|
348
|
-
other_read_mark = @reader.read_marks.create! do |rm|
|
349
|
-
rm.readable_type = 'Foo'
|
350
|
-
rm.readable_id = 42
|
351
|
-
rm.timestamp = 5.years.ago
|
352
|
-
end
|
353
|
-
|
354
|
-
Email.cleanup_read_marks!
|
355
|
-
|
356
|
-
expect(ReadMark.exists?(other_read_mark.id)).to be_truthy
|
357
317
|
end
|
358
318
|
end
|
359
319
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Unread::Reader::Scopes do
|
4
|
+
it 'should define reader_scope' do
|
5
|
+
expect(Reader.reader_scope).to eq Reader.not_foo.not_bar
|
6
|
+
expect(DifferentReader.reader_scope).to eq DifferentReader
|
7
|
+
expect(StiReader.reader_scope).to eq StiReader
|
8
|
+
end
|
9
|
+
end
|
@@ -4,6 +4,7 @@ describe Unread::Reader do
|
|
4
4
|
before :each do
|
5
5
|
@reader = Reader.create! :name => 'David'
|
6
6
|
@other_reader = Reader.create :name => 'Matz'
|
7
|
+
@different_reader = DifferentReader.create! :name => 'Behrooz', :number => @reader.number
|
7
8
|
wait
|
8
9
|
@email1 = Email.create!
|
9
10
|
wait
|
@@ -25,6 +26,25 @@ describe Unread::Reader do
|
|
25
26
|
expect(Reader.have_not_read(@email2)).to eq [@reader, @other_reader]
|
26
27
|
end
|
27
28
|
|
29
|
+
it "should take the type of reader into account" do
|
30
|
+
# even though the id of @reader and @different_reader is the same because
|
31
|
+
# they are different object types, the @email1 should only be marked as
|
32
|
+
# read for @reader.
|
33
|
+
@email1.mark_as_read! :for => @reader
|
34
|
+
|
35
|
+
expect(Reader.have_not_read(@email1)).to eq [@other_reader]
|
36
|
+
expect(Reader.have_not_read(@email1).count).to eq 1
|
37
|
+
|
38
|
+
expect(DifferentReader.have_not_read(@email1)).to eq [@different_reader]
|
39
|
+
expect(DifferentReader.have_not_read(@email1).count).to eq 1
|
40
|
+
|
41
|
+
@email1.mark_as_read! :for => @different_reader
|
42
|
+
|
43
|
+
expect(DifferentReader.have_not_read(@email1).count).to eq 0
|
44
|
+
|
45
|
+
expect(Reader.have_not_read(@email2)).to eq [@reader, @other_reader]
|
46
|
+
end
|
47
|
+
|
28
48
|
it "should not allow invalid parameter" do
|
29
49
|
[ 42, nil, 'foo', :foo, {} ].each do |not_a_readable|
|
30
50
|
expect {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Georg Ledermann
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -126,7 +126,8 @@ description: 'This gem creates a scope for unread objects and adds methods to ma
|
|
126
126
|
objects as read '
|
127
127
|
email:
|
128
128
|
- mail@georg-ledermann.de
|
129
|
-
executables:
|
129
|
+
executables:
|
130
|
+
- console
|
130
131
|
extensions: []
|
131
132
|
extra_rdoc_files: []
|
132
133
|
files:
|
@@ -136,6 +137,8 @@ files:
|
|
136
137
|
- MIT-LICENSE
|
137
138
|
- README.md
|
138
139
|
- Rakefile
|
140
|
+
- UPGRADE.md
|
141
|
+
- bin/console
|
139
142
|
- ci/Gemfile-rails-3-0
|
140
143
|
- ci/Gemfile-rails-3-1
|
141
144
|
- ci/Gemfile-rails-3-2
|
@@ -144,25 +147,33 @@ files:
|
|
144
147
|
- ci/Gemfile-rails-4-2
|
145
148
|
- lib/generators/unread/migration/migration_generator.rb
|
146
149
|
- lib/generators/unread/migration/templates/migration.rb
|
150
|
+
- lib/generators/unread/polymorphic_reader_migration/polymorphic_reader_migration_generator.rb
|
151
|
+
- lib/generators/unread/polymorphic_reader_migration/templates/unread_polymorphic_reader_migration.rb
|
147
152
|
- lib/unread.rb
|
148
153
|
- lib/unread/base.rb
|
154
|
+
- lib/unread/garbage_collector.rb
|
149
155
|
- lib/unread/read_mark.rb
|
150
156
|
- lib/unread/readable.rb
|
151
157
|
- lib/unread/readable_scopes.rb
|
152
158
|
- lib/unread/reader.rb
|
153
159
|
- lib/unread/reader_scopes.rb
|
154
160
|
- lib/unread/version.rb
|
155
|
-
- spec/
|
161
|
+
- spec/app/models/different_reader.rb
|
162
|
+
- spec/app/models/email.rb
|
163
|
+
- spec/app/models/reader.rb
|
164
|
+
- spec/app/models/sti_reader.rb
|
156
165
|
- spec/database.yml
|
157
|
-
- spec/model/email.rb
|
158
|
-
- spec/model/reader.rb
|
159
|
-
- spec/read_mark_spec.rb
|
160
|
-
- spec/readable_spec.rb
|
161
|
-
- spec/reader_spec.rb
|
162
166
|
- spec/spec_helper.rb
|
163
167
|
- spec/support/matchers/perform_queries.rb
|
164
168
|
- spec/support/query_counter.rb
|
169
|
+
- spec/support/spec_migration.rb
|
165
170
|
- spec/support/timecop.rb
|
171
|
+
- spec/unread/base_spec.rb
|
172
|
+
- spec/unread/garbage_collector_spec.rb
|
173
|
+
- spec/unread/read_mark_spec.rb
|
174
|
+
- spec/unread/readable_spec.rb
|
175
|
+
- spec/unread/reader_scopes_spec.rb
|
176
|
+
- spec/unread/reader_spec.rb
|
166
177
|
- unread.gemspec
|
167
178
|
homepage: https://github.com/ledermann/unread
|
168
179
|
licenses:
|
@@ -184,19 +195,24 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
195
|
version: '0'
|
185
196
|
requirements: []
|
186
197
|
rubyforge_project: unread
|
187
|
-
rubygems_version: 2.
|
198
|
+
rubygems_version: 2.5.0
|
188
199
|
signing_key:
|
189
200
|
specification_version: 4
|
190
201
|
summary: Manages read/unread status of ActiveRecord objects
|
191
202
|
test_files:
|
192
|
-
- spec/
|
203
|
+
- spec/app/models/different_reader.rb
|
204
|
+
- spec/app/models/email.rb
|
205
|
+
- spec/app/models/reader.rb
|
206
|
+
- spec/app/models/sti_reader.rb
|
193
207
|
- spec/database.yml
|
194
|
-
- spec/model/email.rb
|
195
|
-
- spec/model/reader.rb
|
196
|
-
- spec/read_mark_spec.rb
|
197
|
-
- spec/readable_spec.rb
|
198
|
-
- spec/reader_spec.rb
|
199
208
|
- spec/spec_helper.rb
|
200
209
|
- spec/support/matchers/perform_queries.rb
|
201
210
|
- spec/support/query_counter.rb
|
211
|
+
- spec/support/spec_migration.rb
|
202
212
|
- spec/support/timecop.rb
|
213
|
+
- spec/unread/base_spec.rb
|
214
|
+
- spec/unread/garbage_collector_spec.rb
|
215
|
+
- spec/unread/read_mark_spec.rb
|
216
|
+
- spec/unread/readable_spec.rb
|
217
|
+
- spec/unread/reader_scopes_spec.rb
|
218
|
+
- spec/unread/reader_spec.rb
|