unread 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/ledermann/unread.png)](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
|