unread 0.1.2 → 0.2.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.
- data/.travis.yml +1 -2
- data/Gemfile +1 -1
- data/README.md +14 -28
- data/Rakefile +1 -1
- data/changelog.md +8 -2
- data/ci/Gemfile.rails-3.0.x +2 -2
- data/ci/Gemfile.rails-3.1.x +2 -2
- data/ci/Gemfile.rails-3.2.x +2 -2
- data/lib/app/models/read_mark.rb +9 -18
- data/lib/generators/unread/migration/migration_generator.rb +23 -0
- data/lib/generators/unread/migration/templates/migration.rb +16 -0
- data/lib/unread/acts_as_readable.rb +94 -94
- data/lib/unread/version.rb +1 -1
- data/test/database.yml +1 -1
- data/test/schema.rb +3 -3
- data/test/test_helper.rb +4 -2
- data/test/unread_test.rb +41 -43
- data/unread.gemspec +5 -5
- metadata +11 -10
- data/ci/Gemfile.rails-2.3.x +0 -7
data/.travis.yml
CHANGED
@@ -3,7 +3,6 @@ rvm:
|
|
3
3
|
- 1.8.7
|
4
4
|
- 1.9.3
|
5
5
|
gemfile:
|
6
|
-
- ci/Gemfile.rails-2.3.x
|
7
6
|
- ci/Gemfile.rails-3.0.x
|
8
7
|
- ci/Gemfile.rails-3.1.x
|
9
8
|
- ci/Gemfile.rails-3.2.x
|
@@ -11,4 +10,4 @@ env:
|
|
11
10
|
- DB=sqlite
|
12
11
|
- DB=mysql
|
13
12
|
before_script:
|
14
|
-
- "mysql -e 'create database myapp_test;' >/dev/null"
|
13
|
+
- "mysql -e 'create database myapp_test;' >/dev/null"
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Unread
|
|
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
|
+
[](https://codeclimate.com/github/ledermann/unread)
|
8
8
|
|
9
9
|
## Features
|
10
10
|
|
@@ -19,7 +19,7 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
|
19
19
|
## Requirements
|
20
20
|
|
21
21
|
* Ruby 1.8.7 or 1.9.3
|
22
|
-
* Rails
|
22
|
+
* Rails 3 (including 3.0, 3.1, 3.2). The use with Rails 2.3 there is a branch named "rails2"
|
23
23
|
* Tested with SQLite and MySQL
|
24
24
|
* Needs a timestamp field in your models (like created_at or updated_at) with a database index on it
|
25
25
|
|
@@ -28,38 +28,24 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
|
28
28
|
|
29
29
|
Step 1: Add this to your Gemfile:
|
30
30
|
|
31
|
-
|
31
|
+
```ruby
|
32
|
+
gem 'unread'
|
33
|
+
```
|
32
34
|
|
33
35
|
and run
|
34
36
|
|
35
|
-
|
37
|
+
```shell
|
38
|
+
bundle
|
39
|
+
```
|
36
40
|
|
37
41
|
|
38
|
-
Step 2:
|
42
|
+
Step 2: Generate and run the migration:
|
39
43
|
|
40
|
-
```
|
41
|
-
|
42
|
-
|
43
|
-
create_table :read_marks, :force => true do |t|
|
44
|
-
t.integer :readable_id
|
45
|
-
t.integer :user_id, :null => false
|
46
|
-
t.string :readable_type, :null => false, :limit => 20
|
47
|
-
t.datetime :timestamp
|
48
|
-
end
|
49
|
-
|
50
|
-
add_index :read_marks, [:user_id, :readable_type, :readable_id]
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.down
|
54
|
-
drop_table :read_marks
|
55
|
-
end
|
56
|
-
end
|
44
|
+
```shell
|
45
|
+
rails g unread:migration
|
46
|
+
rake db:migrate
|
57
47
|
```
|
58
48
|
|
59
|
-
and run the migration:
|
60
|
-
|
61
|
-
rake db:migrate
|
62
|
-
|
63
49
|
|
64
50
|
## Usage
|
65
51
|
|
@@ -122,7 +108,7 @@ current_user = User.find(42)
|
|
122
108
|
Message.unread_by(current_user)
|
123
109
|
```
|
124
110
|
|
125
|
-
Generated query:
|
111
|
+
Generated query:
|
126
112
|
|
127
113
|
```sql
|
128
114
|
SELECT messages.*
|
@@ -148,4 +134,4 @@ There are two other gems/plugins doing a similar job:
|
|
148
134
|
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.
|
149
135
|
|
150
136
|
|
151
|
-
Copyright (c) 2010-2013 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
|
137
|
+
Copyright (c) 2010-2013 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
|
data/Rakefile
CHANGED
data/changelog.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
0.2.0 - WIP
|
2
|
+
|
3
|
+
* Support for Rails 2 dropped
|
4
|
+
* Refactoring
|
5
|
+
* Added migration generator
|
6
|
+
|
1
7
|
0.1.2 - 2013/01/27
|
2
8
|
|
3
9
|
* Scopes: Improved parameter check
|
@@ -39,8 +45,8 @@
|
|
39
45
|
|
40
46
|
0.0.2 - 2011/06/23
|
41
47
|
|
42
|
-
* Fixed scoping for ActiveRecord 2.x
|
48
|
+
* Fixed scoping for ActiveRecord 2.x
|
43
49
|
|
44
50
|
0.0.1 - 2011/06/23
|
45
51
|
|
46
|
-
* Released as Gem
|
52
|
+
* Released as Gem
|
data/ci/Gemfile.rails-3.0.x
CHANGED
data/ci/Gemfile.rails-3.1.x
CHANGED
data/ci/Gemfile.rails-3.2.x
CHANGED
data/lib/app/models/read_mark.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
1
|
class ReadMark < ActiveRecord::Base
|
2
2
|
belongs_to :readable, :polymorphic => true
|
3
3
|
attr_accessible :readable_id, :user_id, :readable_type, :timestamp
|
4
|
-
|
4
|
+
|
5
5
|
validates_presence_of :user_id, :readable_type
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
send scope_method, :single, :conditions => 'readable_id IS NOT NULL'
|
11
|
-
send scope_method, :readable_type, lambda { |readable_type | { :conditions => { :readable_type => readable_type }}}
|
12
|
-
send scope_method, :user, lambda { |user| { :conditions => { :user_id => user.id }}}
|
13
|
-
send scope_method, :older_than, lambda { |timestamp| { :conditions => [ 'timestamp < ?', timestamp] }}
|
6
|
+
|
7
|
+
scope :global, where(:readable_id => nil)
|
8
|
+
scope :single, where('readable_id IS NOT NULL')
|
9
|
+
scope :older_than, lambda { |timestamp| where([ 'timestamp < ?', timestamp]) }
|
14
10
|
|
15
11
|
# Returns the class defined by ActsAsReadable::acts_as_reader
|
16
12
|
def self.reader_class
|
17
|
-
|
18
|
-
user_association.try(:klass)
|
13
|
+
reflect_on_all_associations(:belongs_to).find { |assoc| assoc.name == :user }.try(:klass)
|
19
14
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
else
|
24
|
-
class_inheritable_accessor :readable_classes
|
25
|
-
end
|
26
|
-
end
|
15
|
+
|
16
|
+
class_attribute :readable_classes
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Unread
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "Generates migration for read_markers"
|
9
|
+
source_root File.expand_path('../templates', __FILE__)
|
10
|
+
|
11
|
+
def create_migration_file
|
12
|
+
migration_template 'migration.rb', 'db/migrate/unread_migration'
|
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 UnreadMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :read_marks, :force => true do |t|
|
4
|
+
t.integer :readable_id
|
5
|
+
t.integer :user_id, :null => false
|
6
|
+
t.string :readable_type, :null => false, :limit => 20
|
7
|
+
t.datetime :timestamp
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :read_marks, [:user_id, :readable_type, :readable_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :read_marks
|
15
|
+
end
|
16
|
+
end
|
@@ -2,95 +2,90 @@ module Unread
|
|
2
2
|
def self.included(base)
|
3
3
|
base.extend ActsAsReadable
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
6
|
module ActsAsReadable
|
7
7
|
def acts_as_reader
|
8
8
|
ReadMark.belongs_to :user, :class_name => self.to_s
|
9
|
-
|
9
|
+
|
10
10
|
has_many :read_marks, :dependent => :delete_all, :foreign_key => 'user_id', :inverse_of => :user
|
11
|
-
|
11
|
+
|
12
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
|
13
16
|
(ReadMark.readable_classes || []).each do |klass|
|
14
17
|
klass.mark_as_read! :all, :for => user
|
15
18
|
end
|
16
19
|
end
|
17
|
-
|
20
|
+
|
18
21
|
include ReaderInstanceMethods
|
19
22
|
end
|
20
|
-
|
23
|
+
|
21
24
|
def acts_as_readable(options={})
|
22
|
-
|
23
|
-
class_attribute :readable_options
|
24
|
-
else
|
25
|
-
class_inheritable_accessor :readable_options
|
26
|
-
end
|
25
|
+
class_attribute :readable_options
|
27
26
|
|
28
|
-
options.reverse_merge!(
|
27
|
+
options.reverse_merge!(:on => :updated_at)
|
29
28
|
self.readable_options = options
|
30
|
-
|
29
|
+
|
31
30
|
has_many :read_marks, :as => :readable, :dependent => :delete_all
|
32
|
-
|
31
|
+
|
33
32
|
ReadMark.readable_classes ||= []
|
34
|
-
ReadMark.readable_classes << self unless ReadMark.readable_classes.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
send scope_method, :unread_by, lambda { |user|
|
33
|
+
ReadMark.readable_classes << self unless ReadMark.readable_classes.include?(self)
|
34
|
+
|
35
|
+
scope :join_read_marks, lambda { |user|
|
39
36
|
assert_reader(user)
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
joins "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
|
39
|
+
AND read_marks.readable_id = #{self.table_name}.id
|
40
|
+
AND read_marks.user_id = #{user.id}
|
41
|
+
AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}"
|
42
|
+
}
|
43
|
+
|
44
|
+
scope :unread_by, lambda { |user|
|
45
|
+
result = join_read_marks(user).
|
46
|
+
where('read_marks.id IS NULL')
|
47
|
+
|
46
48
|
if global_time_stamp = user.read_mark_global(self).try(:timestamp)
|
47
|
-
result
|
49
|
+
result = result.where("#{self.table_name}.#{readable_options[:on]} > '#{global_time_stamp.to_s(:db)}'")
|
48
50
|
end
|
51
|
+
|
49
52
|
result
|
50
53
|
}
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
{ :select => "#{self.table_name}.*, read_marks.id AS read_mark_id",
|
56
|
-
:joins => "LEFT JOIN read_marks ON read_marks.readable_type = '#{self.base_class.name}'
|
57
|
-
AND read_marks.readable_id = #{self.table_name}.id
|
58
|
-
AND read_marks.user_id = #{user.id}
|
59
|
-
AND read_marks.timestamp >= #{self.table_name}.#{readable_options[:on]}" }
|
54
|
+
|
55
|
+
scope :with_read_marks_for, lambda { |user|
|
56
|
+
join_read_marks(user).select("#{self.table_name}.*, read_marks.id AS read_mark_id")
|
60
57
|
}
|
58
|
+
|
61
59
|
extend ReadableClassMethods
|
62
60
|
include ReadableInstanceMethods
|
63
61
|
end
|
64
62
|
end
|
65
|
-
|
63
|
+
|
66
64
|
module ReadableClassMethods
|
67
65
|
def mark_as_read!(target, options)
|
68
|
-
raise ArgumentError unless target == :all || target.is_a?(Array)
|
69
|
-
|
70
66
|
user = options[:for]
|
71
67
|
assert_reader(user)
|
72
|
-
|
68
|
+
|
73
69
|
if target == :all
|
74
|
-
|
75
|
-
elsif target.is_a?(Array)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
70
|
+
reset_read_marks_for_user(user)
|
71
|
+
elsif target.is_a?(Array)
|
72
|
+
mark_array_as_read(target, user)
|
73
|
+
else
|
74
|
+
raise ArgumentError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def mark_array_as_read(array, user)
|
79
|
+
ReadMark.transaction do
|
80
|
+
array.each do |obj|
|
81
|
+
raise ArgumentError unless obj.is_a?(self)
|
82
|
+
|
83
|
+
rm = obj.read_marks.where(:user_id => user.id).first || obj.read_marks.build(:user_id => user.id)
|
84
|
+
rm.timestamp = obj.send(readable_options[:on])
|
85
|
+
rm.save!
|
85
86
|
end
|
86
87
|
end
|
87
88
|
end
|
88
|
-
|
89
|
-
def set_read_mark(user, timestamp)
|
90
|
-
rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.base_class.name)
|
91
|
-
rm.timestamp = timestamp
|
92
|
-
rm.save!
|
93
|
-
end
|
94
89
|
|
95
90
|
# A scope with all items accessable for the given user
|
96
91
|
# It's used in cleanup_read_marks! to support a filtered cleanup
|
@@ -107,64 +102,69 @@ module Unread
|
|
107
102
|
|
108
103
|
def cleanup_read_marks!
|
109
104
|
assert_reader_class
|
110
|
-
|
105
|
+
|
111
106
|
ReadMark.reader_class.find_each do |user|
|
112
107
|
ReadMark.transaction do
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# Delete markers OLDER than this timestamp and move the global timestamp for this user
|
118
|
-
user.read_marks.readable_type(self.base_class.name).single.older_than(oldest_timestamp).delete_all
|
119
|
-
set_read_mark(user, oldest_timestamp - 1.second)
|
108
|
+
if oldest_timestamp = read_scope(user).unread_by(user).minimum(readable_options[:on])
|
109
|
+
# There are unread items, so update the global read_mark for this user to the oldest
|
110
|
+
# unread item and delete older read_marks
|
111
|
+
update_read_marks_for_user(user, oldest_timestamp)
|
120
112
|
else
|
121
|
-
# There is no unread item, so
|
122
|
-
|
113
|
+
# There is no unread item, so deletes all markers and move global timestamp
|
114
|
+
reset_read_marks_for_user(user)
|
123
115
|
end
|
124
116
|
end
|
125
117
|
end
|
126
118
|
end
|
127
|
-
|
128
|
-
def reset_read_marks!(user = :all)
|
129
|
-
assert_reader_class
|
130
119
|
|
120
|
+
def update_read_marks_for_user(user, timestamp)
|
121
|
+
# Delete markers OLDER than the given timestamp
|
122
|
+
user.read_marks.where(:readable_type => self.base_class.name).single.older_than(timestamp).delete_all
|
123
|
+
|
124
|
+
# Change the global timestamp for this user
|
125
|
+
rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.base_class.name)
|
126
|
+
rm.timestamp = timestamp - 1.second
|
127
|
+
rm.save!
|
128
|
+
end
|
129
|
+
|
130
|
+
def reset_read_marks_for_all
|
131
131
|
ReadMark.transaction do
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
FROM #{ReadMark.reader_class.table_name}
|
139
|
-
")
|
140
|
-
else
|
141
|
-
ReadMark.delete_all :readable_type => self.base_class.name, :user_id => user.id
|
142
|
-
ReadMark.create! :readable_type => self.base_class.name, :user_id => user.id, :timestamp => Time.now
|
143
|
-
end
|
132
|
+
ReadMark.delete_all :readable_type => self.base_class.name
|
133
|
+
ReadMark.connection.execute <<-EOT
|
134
|
+
INSERT INTO read_marks (user_id, readable_type, timestamp)
|
135
|
+
SELECT id, '#{self.base_class.name}', '#{Time.now.to_s(:db)}'
|
136
|
+
FROM #{ReadMark.reader_class.table_name}
|
137
|
+
EOT
|
144
138
|
end
|
145
|
-
true
|
146
139
|
end
|
147
|
-
|
140
|
+
|
141
|
+
def reset_read_marks_for_user(user)
|
142
|
+
assert_reader(user)
|
143
|
+
|
144
|
+
ReadMark.transaction do
|
145
|
+
ReadMark.delete_all :readable_type => self.base_class.name, :user_id => user.id
|
146
|
+
ReadMark.create! :readable_type => self.base_class.name, :user_id => user.id, :timestamp => Time.now
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
148
150
|
def assert_reader(user)
|
149
151
|
assert_reader_class
|
150
|
-
|
152
|
+
|
151
153
|
raise ArgumentError, "Class #{user.class.name} is not registered by acts_as_reader!" unless user.is_a?(ReadMark.reader_class)
|
152
154
|
raise ArgumentError, "The given user has no id!" unless user.id
|
153
155
|
end
|
154
|
-
|
156
|
+
|
155
157
|
def assert_reader_class
|
156
|
-
unless ReadMark.reader_class
|
157
|
-
raise RuntimeError, 'There is no class using acts_as_reader!'
|
158
|
-
end
|
158
|
+
raise RuntimeError, 'There is no class using acts_as_reader!' unless ReadMark.reader_class
|
159
159
|
end
|
160
160
|
end
|
161
|
-
|
162
|
-
module ReadableInstanceMethods
|
161
|
+
|
162
|
+
module ReadableInstanceMethods
|
163
163
|
def unread?(user)
|
164
164
|
if self.respond_to?(:read_mark_id)
|
165
165
|
# For use with scope "with_read_marks_for"
|
166
166
|
return false if self.read_mark_id
|
167
|
-
|
167
|
+
|
168
168
|
if global_timestamp = user.read_mark_global(self.class).try(:timestamp)
|
169
169
|
self.send(readable_options[:on]) > global_timestamp
|
170
170
|
else
|
@@ -174,11 +174,11 @@ module Unread
|
|
174
174
|
self.class.unread_by(user).exists?(self)
|
175
175
|
end
|
176
176
|
end
|
177
|
-
|
177
|
+
|
178
178
|
def mark_as_read!(options)
|
179
179
|
user = options[:for]
|
180
180
|
self.class.assert_reader(user)
|
181
|
-
|
181
|
+
|
182
182
|
ReadMark.transaction do
|
183
183
|
if unread?(user)
|
184
184
|
rm = read_mark(user) || read_marks.build(:user_id => user.id)
|
@@ -189,15 +189,15 @@ module Unread
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def read_mark(user)
|
192
|
-
read_marks.
|
192
|
+
read_marks.where(:user_id => user.id).first
|
193
193
|
end
|
194
194
|
end
|
195
|
-
|
195
|
+
|
196
196
|
module ReaderInstanceMethods
|
197
197
|
def read_mark_global(klass)
|
198
198
|
instance_var_name = "@read_mark_global_#{klass.name.gsub('::','_')}"
|
199
199
|
instance_variable_get(instance_var_name) || begin # memoize
|
200
|
-
obj = self.read_marks.readable_type
|
200
|
+
obj = self.read_marks.where(:readable_type => klass.base_class.name).global.first
|
201
201
|
instance_variable_set(instance_var_name, obj)
|
202
202
|
end
|
203
203
|
end
|
data/lib/unread/version.rb
CHANGED
data/test/database.yml
CHANGED
data/test/schema.rb
CHANGED
@@ -2,14 +2,14 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
2
2
|
create_table :readers, :force => true do |t|
|
3
3
|
t.string :name
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
6
|
create_table :emails, :force => true do |t|
|
7
7
|
t.string :subject
|
8
8
|
t.text :content
|
9
9
|
t.datetime :created_at
|
10
10
|
t.datetime :updated_at
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
create_table :read_marks, :force => true do |t|
|
14
14
|
t.integer :readable_id
|
15
15
|
t.integer :user_id, :null => false
|
@@ -17,4 +17,4 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
17
17
|
t.datetime :timestamp
|
18
18
|
end
|
19
19
|
add_index :read_marks, [:user_id, :readable_type, :readable_id]
|
20
|
-
end
|
20
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -2,7 +2,7 @@ require 'test/unit'
|
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/test_case'
|
4
4
|
require 'active_record'
|
5
|
-
require '
|
5
|
+
require 'timecop'
|
6
6
|
|
7
7
|
configs = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
8
8
|
ActiveRecord::Base.configurations = configs
|
@@ -12,6 +12,8 @@ ActiveRecord::Base.establish_connection(db_name)
|
|
12
12
|
ActiveRecord::Migration.verbose = false
|
13
13
|
load(File.dirname(__FILE__) + "/schema.rb")
|
14
14
|
|
15
|
+
require 'unread'
|
16
|
+
|
15
17
|
class Reader < ActiveRecord::Base
|
16
18
|
acts_as_reader
|
17
19
|
end
|
@@ -20,4 +22,4 @@ class Email < ActiveRecord::Base
|
|
20
22
|
acts_as_readable :on => :updated_at
|
21
23
|
end
|
22
24
|
|
23
|
-
puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
25
|
+
puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
data/test/unread_test.rb
CHANGED
@@ -9,21 +9,21 @@ class UnreadTest < ActiveSupport::TestCase
|
|
9
9
|
wait
|
10
10
|
@email2 = Email.create!
|
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
|
18
|
-
|
18
|
+
|
19
19
|
def test_schema_has_loaded_correctly
|
20
20
|
assert_equal [@email1, @email2], Email.all
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def test_readable_classes
|
24
24
|
assert_equal [ Email ], ReadMark.readable_classes
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def test_reader_class
|
28
28
|
assert_equal Reader, ReadMark.reader_class
|
29
29
|
end
|
@@ -31,37 +31,37 @@ class UnreadTest < ActiveSupport::TestCase
|
|
31
31
|
def test_scope
|
32
32
|
assert_equal [@email1, @email2], Email.unread_by(@reader)
|
33
33
|
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
34
|
-
|
34
|
+
|
35
35
|
assert_equal 2, Email.unread_by(@reader).count
|
36
36
|
assert_equal 2, Email.unread_by(@other_reader).count
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def test_with_read_marks_for
|
40
40
|
@email1.mark_as_read! :for => @reader
|
41
|
-
|
41
|
+
|
42
42
|
emails = Email.with_read_marks_for(@reader).all
|
43
43
|
|
44
44
|
assert emails[0].read_mark_id.present?
|
45
45
|
assert emails[1].read_mark_id.nil?
|
46
|
-
|
46
|
+
|
47
47
|
assert_equal false, emails[0].unread?(@reader)
|
48
48
|
assert_equal true, emails[1].unread?(@reader)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def test_scope_param_check
|
52
52
|
[ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
|
53
53
|
assert_raise(ArgumentError) { Email.unread_by(not_a_reader)}
|
54
54
|
assert_raise(ArgumentError) { Email.with_read_marks_for(not_a_reader)}
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
unsaved_reader = Reader.new
|
58
58
|
assert_raise(ArgumentError) { Email.unread_by(unsaved_reader)}
|
59
59
|
assert_raise(ArgumentError) { Email.with_read_marks_for(unsaved_reader)}
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
def test_scope_after_reset
|
63
63
|
@email1.mark_as_read! :for => @reader
|
64
|
-
|
64
|
+
|
65
65
|
assert_equal [@email2], Email.unread_by(@reader)
|
66
66
|
assert_equal 1, Email.unread_by(@reader).count
|
67
67
|
end
|
@@ -69,12 +69,12 @@ class UnreadTest < ActiveSupport::TestCase
|
|
69
69
|
def test_unread_after_create
|
70
70
|
assert_equal true, @email1.unread?(@reader)
|
71
71
|
assert_equal true, @email1.unread?(@other_reader)
|
72
|
-
|
72
|
+
|
73
73
|
assert_raise(ArgumentError) {
|
74
74
|
@email1.unread?(42)
|
75
75
|
}
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
def test_unread_after_update
|
79
79
|
@email1.mark_as_read! :for => @reader
|
80
80
|
wait
|
@@ -82,86 +82,84 @@ class UnreadTest < ActiveSupport::TestCase
|
|
82
82
|
|
83
83
|
assert_equal true, @email1.unread?(@reader)
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
def test_mark_as_read
|
87
87
|
@email1.mark_as_read! :for => @reader
|
88
|
-
|
88
|
+
|
89
89
|
assert_equal false, @email1.unread?(@reader)
|
90
90
|
assert_equal [@email2], Email.unread_by(@reader)
|
91
|
-
|
91
|
+
|
92
92
|
assert_equal true, @email1.unread?(@other_reader)
|
93
93
|
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
94
|
-
|
94
|
+
|
95
95
|
assert_equal 1, @reader.read_marks.single.count
|
96
96
|
assert_equal @email1, @reader.read_marks.single.first.readable
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
def test_mark_as_read_multiple
|
100
100
|
assert_equal true, @email1.unread?(@reader)
|
101
101
|
assert_equal true, @email2.unread?(@reader)
|
102
|
-
|
102
|
+
|
103
103
|
Email.mark_as_read! [ @email1, @email2 ], :for => @reader
|
104
|
-
|
104
|
+
|
105
105
|
assert_equal false, @email1.unread?(@reader)
|
106
106
|
assert_equal false, @email2.unread?(@reader)
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
def test_mark_as_read_with_marked_all
|
110
110
|
wait
|
111
|
-
|
111
|
+
|
112
112
|
Email.mark_as_read! :all, :for => @reader
|
113
113
|
@email1.mark_as_read! :for => @reader
|
114
|
-
|
114
|
+
|
115
115
|
assert_equal [], @reader.read_marks.single
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
def test_mark_as_read_twice
|
119
119
|
@email1.mark_as_read! :for => @reader
|
120
120
|
@email1.mark_as_read! :for => @reader
|
121
|
-
|
121
|
+
|
122
122
|
assert_equal 1, @reader.read_marks.single.count
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
def test_mark_all_as_read
|
126
126
|
Email.mark_as_read! :all, :for => @reader
|
127
127
|
assert_equal Time.now.to_s, @reader.read_mark_global(Email).try(:timestamp).to_s
|
128
|
-
|
128
|
+
|
129
129
|
assert_equal [], @reader.read_marks.single
|
130
130
|
assert_equal 0, ReadMark.single.count
|
131
131
|
assert_equal 2, ReadMark.global.count
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
def test_cleanup_read_marks
|
135
135
|
assert_equal 0, @reader.read_marks.single.count
|
136
|
-
|
136
|
+
|
137
137
|
@email1.mark_as_read! :for => @reader
|
138
|
-
|
138
|
+
|
139
139
|
assert_equal [@email2], Email.unread_by(@reader)
|
140
140
|
assert_equal 1, @reader.read_marks.single.count
|
141
|
-
|
142
|
-
Email.cleanup_read_marks!
|
143
|
-
|
141
|
+
|
142
|
+
Email.cleanup_read_marks!
|
143
|
+
|
144
144
|
@reader.reload
|
145
145
|
assert_equal 0, @reader.read_marks.single.count
|
146
146
|
end
|
147
|
-
|
147
|
+
|
148
148
|
def test_cleanup_read_marks_not_delete_from_other_readables
|
149
149
|
other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
|
150
150
|
Email.cleanup_read_marks!
|
151
151
|
assert_equal true, ReadMark.exists?(other_read_mark.id)
|
152
152
|
end
|
153
|
-
|
153
|
+
|
154
154
|
def test_reset_read_marks_for_all
|
155
|
-
Email.
|
156
|
-
|
155
|
+
Email.reset_read_marks_for_all
|
156
|
+
|
157
157
|
assert_equal 0, ReadMark.single.count
|
158
158
|
assert_equal 2, ReadMark.global.count
|
159
159
|
end
|
160
|
-
|
160
|
+
|
161
161
|
private
|
162
162
|
def wait
|
163
|
-
|
164
|
-
now = Time.now + 1.second
|
165
|
-
Time.stubs(:now).returns(now)
|
163
|
+
Timecop.travel(1.minute)
|
166
164
|
end
|
167
|
-
end
|
165
|
+
end
|
data/unread.gemspec
CHANGED
@@ -17,11 +17,11 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
|
-
|
21
|
-
s.add_dependency 'activerecord', '>=
|
22
|
-
|
20
|
+
|
21
|
+
s.add_dependency 'activerecord', '>= 3'
|
22
|
+
|
23
23
|
s.add_development_dependency 'rake'
|
24
|
-
s.add_development_dependency '
|
24
|
+
s.add_development_dependency 'timecop'
|
25
25
|
s.add_development_dependency 'sqlite3'
|
26
26
|
s.add_development_dependency 'mysql2'
|
27
|
-
end
|
27
|
+
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: unread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Georg Ledermann
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
prerelease: false
|
@@ -17,7 +17,7 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ! '>='
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: '3'
|
21
21
|
none: false
|
22
22
|
type: :runtime
|
23
23
|
name: activerecord
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
requirements:
|
26
26
|
- - ! '>='
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: '3'
|
29
29
|
none: false
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
prerelease: false
|
@@ -47,17 +47,17 @@ dependencies:
|
|
47
47
|
prerelease: false
|
48
48
|
version_requirements: !ruby/object:Gem::Requirement
|
49
49
|
requirements:
|
50
|
-
- -
|
50
|
+
- - ! '>='
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: 0
|
52
|
+
version: '0'
|
53
53
|
none: false
|
54
54
|
type: :development
|
55
|
-
name:
|
55
|
+
name: timecop
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
|
-
- -
|
58
|
+
- - ! '>='
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: 0
|
60
|
+
version: '0'
|
61
61
|
none: false
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
prerelease: false
|
@@ -106,11 +106,12 @@ files:
|
|
106
106
|
- README.md
|
107
107
|
- Rakefile
|
108
108
|
- changelog.md
|
109
|
-
- ci/Gemfile.rails-2.3.x
|
110
109
|
- ci/Gemfile.rails-3.0.x
|
111
110
|
- ci/Gemfile.rails-3.1.x
|
112
111
|
- ci/Gemfile.rails-3.2.x
|
113
112
|
- lib/app/models/read_mark.rb
|
113
|
+
- lib/generators/unread/migration/migration_generator.rb
|
114
|
+
- lib/generators/unread/migration/templates/migration.rb
|
114
115
|
- lib/unread.rb
|
115
116
|
- lib/unread/acts_as_readable.rb
|
116
117
|
- lib/unread/version.rb
|