unread 0.0.5 → 0.0.6
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.
- data/.travis.yml +1 -1
- data/README.md +16 -20
- data/ci/Gemfile.rails-3.1.x +1 -1
- data/lib/app/models/read_mark.rb +8 -3
- data/lib/unread/acts_as_readable.rb +2 -2
- data/lib/unread/version.rb +1 -1
- data/test/schema.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unread_test.rb +43 -43
- data/unread.gemspec +1 -7
- metadata +9 -9
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
Unread
|
2
2
|
======
|
3
3
|
|
4
|
-
|
4
|
+
Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
5
5
|
|
6
6
|
[](http://travis-ci.org/ledermann/unread)
|
7
7
|
|
8
|
+
|
8
9
|
## Features
|
9
10
|
|
10
|
-
* Manages unread records for anything you want
|
11
|
-
* Supports
|
12
|
-
* Supports
|
13
|
-
* Gives you a
|
11
|
+
* Manages unread records for anything you want users to read (like messages, documents, comments etc.)
|
12
|
+
* Supports _mark as read_ to mark a **single** record as read
|
13
|
+
* Supports _mark all as read__ to mark **all** records as read in a single step
|
14
|
+
* Gives you a scope to get the unread records for a given user
|
14
15
|
* Needs only one additional database table
|
15
16
|
* Most important: Great performance
|
16
17
|
|
17
18
|
|
18
19
|
## Requirements
|
19
20
|
|
20
|
-
* Ruby 1.8.7 or 1.9.
|
21
|
-
*
|
21
|
+
* Ruby 1.8.7 or 1.9.x
|
22
|
+
* Rails 2.3.x, 3.0.x, 3.1.x (tested with SQLite and MySQL)
|
22
23
|
* Needs a timestamp field in your models (e.g. created_at) with a database index on it
|
23
24
|
|
24
25
|
|
@@ -28,7 +29,7 @@ Step 1: Add this to your Gemfile:
|
|
28
29
|
|
29
30
|
gem 'unread'
|
30
31
|
|
31
|
-
|
32
|
+
and run
|
32
33
|
|
33
34
|
bundle
|
34
35
|
|
@@ -87,16 +88,16 @@ Step 2: Add this migration:
|
|
87
88
|
|
88
89
|
## How does it work?
|
89
90
|
|
90
|
-
The main idea of this gem is to manage a list of read items for every
|
91
|
+
The main idea of this gem is to manage a list of read items for every reader **after** a certain timestamp.
|
91
92
|
|
92
|
-
The gem defines a
|
93
|
+
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.
|
93
94
|
|
94
95
|
It will be ensured that the list of read items will not grow up too much:
|
95
96
|
|
96
|
-
* If a user uses "mark all as read", his list
|
97
|
+
* If a user uses "mark all as read", his list gets deleted and the timestamp is set to the current time.
|
97
98
|
* 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.
|
98
99
|
|
99
|
-
Overall, this gem can be used for large
|
100
|
+
Overall, this gem can be used for large data. Please have a look at the generated SQL queries, here is an example:
|
100
101
|
|
101
102
|
# Assuming we have a user who has marked all messages as read on 2010-10-20 08:50
|
102
103
|
current_user = User.find(42)
|
@@ -114,22 +115,17 @@ Overall, this gem can be used for large tables, too. If you are in doubt, look a
|
|
114
115
|
# WHERE read_marks.id IS NULL
|
115
116
|
# AND messages.created_at > '2010-10-20 08:50:00'
|
116
117
|
|
117
|
-
|
118
|
+
Hint: You should add a database index on `messages.created_at`.
|
118
119
|
|
119
120
|
|
120
121
|
## Similar tools
|
121
122
|
|
122
|
-
There
|
123
|
+
There are two other gems/plugins doing a similar job:
|
123
124
|
|
124
125
|
* http://github.com/jhnvz/mark_as_read
|
125
126
|
* http://github.com/mbleigh/acts-as-readable
|
126
127
|
|
127
|
-
Unfortunately, both of them have a lack of performance, because they calculate the unread records doing a
|
128
|
-
|
129
|
-
|
130
|
-
## TODO
|
131
|
-
|
132
|
-
* Add more documentation
|
128
|
+
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.
|
133
129
|
|
134
130
|
|
135
131
|
Copyright (c) 2010,2011 Georg Ledermann, released under the MIT license
|
data/ci/Gemfile.rails-3.1.x
CHANGED
data/lib/app/models/read_mark.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
class ReadMark < ActiveRecord::Base
|
2
|
-
belongs_to :user
|
3
2
|
belongs_to :readable, :polymorphic => true
|
4
3
|
|
5
4
|
validates_presence_of :user_id, :readable_type
|
@@ -11,10 +10,16 @@ class ReadMark < ActiveRecord::Base
|
|
11
10
|
send scope_method, :readable_type, lambda { |readable_type | { :conditions => { :readable_type => readable_type }}}
|
12
11
|
send scope_method, :user, lambda { |user| { :conditions => { :user_id => user.id }}}
|
13
12
|
send scope_method, :older_than, lambda { |timestamp| { :conditions => [ 'timestamp < ?', timestamp] }}
|
13
|
+
|
14
|
+
# Returns the class defined by ActsAsReadable::acts_as_reader
|
15
|
+
def self.reader_class
|
16
|
+
user_association = reflect_on_all_associations(:belongs_to).find { |assoc| assoc.name == :user }
|
17
|
+
user_association.try(:klass)
|
18
|
+
end
|
14
19
|
|
15
20
|
if respond_to?(:class_attribute)
|
16
|
-
class_attribute :
|
21
|
+
class_attribute :readable_classes
|
17
22
|
else
|
18
|
-
class_inheritable_accessor :
|
23
|
+
class_inheritable_accessor :readable_classes
|
19
24
|
end
|
20
25
|
end
|
@@ -5,9 +5,9 @@ module Unread
|
|
5
5
|
|
6
6
|
module ActsAsReadable
|
7
7
|
def acts_as_reader
|
8
|
-
ReadMark.
|
8
|
+
ReadMark.belongs_to :user, :class_name => self.to_s
|
9
9
|
|
10
|
-
has_many :read_marks, :dependent => :delete_all
|
10
|
+
has_many :read_marks, :dependent => :delete_all, :foreign_key => 'user_id'
|
11
11
|
|
12
12
|
after_create do |user|
|
13
13
|
(ReadMark.readable_classes || []).each do |klass|
|
data/lib/unread/version.rb
CHANGED
data/test/schema.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/unread_test.rb
CHANGED
@@ -2,8 +2,8 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class UnreadTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
5
|
-
@
|
6
|
-
@
|
5
|
+
@reader = Reader.create! :name => 'David'
|
6
|
+
@other_reader = Reader.create :name => 'Matz'
|
7
7
|
wait
|
8
8
|
@email1 = Email.create!
|
9
9
|
wait
|
@@ -11,7 +11,7 @@ class UnreadTest < ActiveSupport::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def teardown
|
14
|
-
|
14
|
+
Reader.delete_all
|
15
15
|
Email.delete_all
|
16
16
|
ReadMark.delete_all
|
17
17
|
end
|
@@ -25,15 +25,15 @@ class UnreadTest < ActiveSupport::TestCase
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_reader_class
|
28
|
-
assert_equal
|
28
|
+
assert_equal Reader, ReadMark.reader_class
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_scope
|
32
|
-
assert_equal [@email1, @email2], Email.unread_by(@
|
33
|
-
assert_equal [@email1, @email2], Email.unread_by(@
|
32
|
+
assert_equal [@email1, @email2], Email.unread_by(@reader)
|
33
|
+
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
34
34
|
|
35
|
-
assert_equal 2, Email.unread_by(@
|
36
|
-
assert_equal 2, Email.unread_by(@
|
35
|
+
assert_equal 2, Email.unread_by(@reader).count
|
36
|
+
assert_equal 2, Email.unread_by(@other_reader).count
|
37
37
|
|
38
38
|
assert_raise(ArgumentError) {
|
39
39
|
Email.unread_by(42)
|
@@ -41,15 +41,15 @@ class UnreadTest < ActiveSupport::TestCase
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_scope_after_reset
|
44
|
-
@email1.mark_as_read! :for => @
|
44
|
+
@email1.mark_as_read! :for => @reader
|
45
45
|
|
46
|
-
assert_equal [@email2], Email.unread_by(@
|
47
|
-
assert_equal 1, Email.unread_by(@
|
46
|
+
assert_equal [@email2], Email.unread_by(@reader)
|
47
|
+
assert_equal 1, Email.unread_by(@reader).count
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_unread_after_create
|
51
|
-
assert_equal true, @email1.unread?(@
|
52
|
-
assert_equal true, @email1.unread?(@
|
51
|
+
assert_equal true, @email1.unread?(@reader)
|
52
|
+
assert_equal true, @email1.unread?(@other_reader)
|
53
53
|
|
54
54
|
assert_raise(ArgumentError) {
|
55
55
|
@email1.unread?(42)
|
@@ -57,77 +57,77 @@ class UnreadTest < ActiveSupport::TestCase
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_unread_after_update
|
60
|
-
@email1.mark_as_read! :for => @
|
60
|
+
@email1.mark_as_read! :for => @reader
|
61
61
|
wait
|
62
62
|
@email1.update_attributes! :subject => 'changed'
|
63
63
|
|
64
|
-
assert_equal true, @email1.unread?(@
|
64
|
+
assert_equal true, @email1.unread?(@reader)
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_mark_as_read
|
68
|
-
@email1.mark_as_read! :for => @
|
68
|
+
@email1.mark_as_read! :for => @reader
|
69
69
|
|
70
|
-
assert_equal false, @email1.unread?(@
|
71
|
-
assert_equal [@email2], Email.unread_by(@
|
70
|
+
assert_equal false, @email1.unread?(@reader)
|
71
|
+
assert_equal [@email2], Email.unread_by(@reader)
|
72
72
|
|
73
|
-
assert_equal true, @email1.unread?(@
|
74
|
-
assert_equal [@email1, @email2], Email.unread_by(@
|
73
|
+
assert_equal true, @email1.unread?(@other_reader)
|
74
|
+
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
75
75
|
|
76
|
-
assert_equal 1, @
|
77
|
-
assert_equal @email1, @
|
76
|
+
assert_equal 1, @reader.read_marks.single.count
|
77
|
+
assert_equal @email1, @reader.read_marks.single.first.readable
|
78
78
|
end
|
79
79
|
|
80
80
|
def test_mark_as_read_multiple
|
81
|
-
assert_equal true, @email1.unread?(@
|
82
|
-
assert_equal true, @email2.unread?(@
|
81
|
+
assert_equal true, @email1.unread?(@reader)
|
82
|
+
assert_equal true, @email2.unread?(@reader)
|
83
83
|
|
84
|
-
Email.mark_as_read! [ @email1, @email2 ], :for => @
|
84
|
+
Email.mark_as_read! [ @email1, @email2 ], :for => @reader
|
85
85
|
|
86
|
-
assert_equal false, @email1.unread?(@
|
87
|
-
assert_equal false, @email2.unread?(@
|
86
|
+
assert_equal false, @email1.unread?(@reader)
|
87
|
+
assert_equal false, @email2.unread?(@reader)
|
88
88
|
end
|
89
89
|
|
90
90
|
def test_mark_as_read_with_marked_all
|
91
91
|
wait
|
92
92
|
|
93
|
-
Email.mark_as_read! :all, :for => @
|
94
|
-
@email1.mark_as_read! :for => @
|
93
|
+
Email.mark_as_read! :all, :for => @reader
|
94
|
+
@email1.mark_as_read! :for => @reader
|
95
95
|
|
96
|
-
assert_equal [], @
|
96
|
+
assert_equal [], @reader.read_marks.single
|
97
97
|
end
|
98
98
|
|
99
99
|
def test_mark_as_read_twice
|
100
|
-
@email1.mark_as_read! :for => @
|
101
|
-
@email1.mark_as_read! :for => @
|
100
|
+
@email1.mark_as_read! :for => @reader
|
101
|
+
@email1.mark_as_read! :for => @reader
|
102
102
|
|
103
|
-
assert_equal 1, @
|
103
|
+
assert_equal 1, @reader.read_marks.single.count
|
104
104
|
end
|
105
105
|
|
106
106
|
def test_mark_all_as_read
|
107
|
-
Email.mark_as_read! :all, :for => @
|
108
|
-
assert_equal Time.now.to_s, Email.read_mark(@
|
107
|
+
Email.mark_as_read! :all, :for => @reader
|
108
|
+
assert_equal Time.now.to_s, Email.read_mark(@reader).timestamp.to_s
|
109
109
|
|
110
|
-
assert_equal [], @
|
110
|
+
assert_equal [], @reader.read_marks.single
|
111
111
|
assert_equal 0, ReadMark.single.count
|
112
112
|
assert_equal 2, ReadMark.global.count
|
113
113
|
end
|
114
114
|
|
115
115
|
def test_cleanup_read_marks
|
116
|
-
assert_equal 0, @
|
116
|
+
assert_equal 0, @reader.read_marks.single.count
|
117
117
|
|
118
|
-
@email1.mark_as_read! :for => @
|
118
|
+
@email1.mark_as_read! :for => @reader
|
119
119
|
|
120
|
-
assert_equal [@email2], Email.unread_by(@
|
121
|
-
assert_equal 1, @
|
120
|
+
assert_equal [@email2], Email.unread_by(@reader)
|
121
|
+
assert_equal 1, @reader.read_marks.single.count
|
122
122
|
|
123
123
|
Email.cleanup_read_marks!
|
124
124
|
|
125
|
-
@
|
126
|
-
assert_equal 0, @
|
125
|
+
@reader.reload
|
126
|
+
assert_equal 0, @reader.read_marks.single.count
|
127
127
|
end
|
128
128
|
|
129
129
|
def test_cleanup_read_marks_not_delete_from_other_readables
|
130
|
-
other_read_mark = @
|
130
|
+
other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
|
131
131
|
Email.cleanup_read_marks!
|
132
132
|
assert_equal true, ReadMark.exists?(other_read_mark.id)
|
133
133
|
end
|
data/unread.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
3
|
require "unread/version"
|
4
|
-
require 'active_record/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
6
|
s.name = "unread"
|
@@ -24,10 +23,5 @@ Gem::Specification.new do |s|
|
|
24
23
|
s.add_development_dependency 'rake'
|
25
24
|
s.add_development_dependency 'mocha'
|
26
25
|
s.add_development_dependency 'sqlite3'
|
27
|
-
|
28
|
-
if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR >= 1
|
29
|
-
s.add_development_dependency 'mysql2', '>= 0.3.6'
|
30
|
-
else
|
31
|
-
s.add_development_dependency 'mysql2', '~> 0.2.11'
|
32
|
-
end
|
26
|
+
s.add_development_dependency 'mysql2'
|
33
27
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 6
|
10
|
+
version: 0.0.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Georg Ledermann
|
@@ -15,7 +15,8 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-11-11 00:00:00 +01:00
|
19
|
+
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: activerecord
|
@@ -82,12 +83,10 @@ dependencies:
|
|
82
83
|
requirements:
|
83
84
|
- - ">="
|
84
85
|
- !ruby/object:Gem::Version
|
85
|
-
hash:
|
86
|
+
hash: 3
|
86
87
|
segments:
|
87
88
|
- 0
|
88
|
-
|
89
|
-
- 6
|
90
|
-
version: 0.3.6
|
89
|
+
version: "0"
|
91
90
|
type: :development
|
92
91
|
version_requirements: *id005
|
93
92
|
description: "This gem creates a scope for unread objects and adds methods to mark objects as read "
|
@@ -121,6 +120,7 @@ files:
|
|
121
120
|
- test/unread_test.rb
|
122
121
|
- uninstall.rb
|
123
122
|
- unread.gemspec
|
123
|
+
has_rdoc: true
|
124
124
|
homepage: ""
|
125
125
|
licenses: []
|
126
126
|
|
@@ -150,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
150
|
requirements: []
|
151
151
|
|
152
152
|
rubyforge_project: unread
|
153
|
-
rubygems_version: 1.
|
153
|
+
rubygems_version: 1.6.2
|
154
154
|
signing_key:
|
155
155
|
specification_version: 3
|
156
156
|
summary: Manages read/unread status of ActiveRecord objects
|