unread 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/MIT-LICENSE +3 -1
- data/README.md +9 -2
- data/Rakefile +3 -12
- data/ci/Gemfile-rails-3-0 +4 -1
- data/ci/Gemfile-rails-3-1 +4 -1
- data/ci/Gemfile-rails-3-2 +4 -1
- data/ci/Gemfile-rails-4-0 +4 -1
- data/ci/Gemfile-rails-4-1 +9 -0
- data/lib/generators/unread/migration/migration_generator.rb +1 -1
- data/lib/unread/readable.rb +3 -1
- data/lib/unread/scopes.rb +1 -1
- data/lib/unread/version.rb +1 -1
- data/spec/base_spec.rb +38 -0
- data/spec/model/email.rb +4 -0
- data/spec/model/reader.rb +4 -0
- data/spec/read_mark_spec.rb +7 -0
- data/spec/readable_spec.rb +206 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/support/matchers/perform_queries.rb +22 -0
- data/spec/support/query_counter.rb +17 -0
- data/spec/support/timecop.rb +3 -0
- data/unread.gemspec +3 -0
- metadata +64 -12
- data/changelog.md +0 -75
- data/test/database.yml +0 -3
- data/test/schema.rb +0 -20
- data/test/test_helper.rb +0 -27
- data/test/unread_test.rb +0 -166
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62f6d5dbe191941fe9f5b358318fefbf93ad0c20
|
4
|
+
data.tar.gz: 80e1d361fc919f39f33142283b2dfe8d1d9376a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8808316873efd20db90cb45b9bbf14e4bc6fb3336f69d3f2480bfe8feaee04a21c39a44672d28c892e6365ceb26440525c36283e27d6508baec32f8e2aa72246
|
7
|
+
data.tar.gz: a55db92bee6d0b5969cb244655137e08afc7438a8abedbcb65f92a44d05c77ce60dad18d677e64c745b3b831063e08a5076773d717773d30a1685f74d2735f1e
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -5,6 +5,8 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
|
5
5
|
|
6
6
|
[![Build Status](https://travis-ci.org/ledermann/unread.png?branch=master)](https://travis-ci.org/ledermann/unread)
|
7
7
|
[![Code Climate](https://codeclimate.com/github/ledermann/unread.png)](https://codeclimate.com/github/ledermann/unread)
|
8
|
+
[![Coverage Status](https://coveralls.io/repos/ledermann/unread/badge.png)](https://coveralls.io/r/ledermann/unread)
|
9
|
+
|
8
10
|
|
9
11
|
## Features
|
10
12
|
|
@@ -18,11 +20,16 @@ Ruby gem to manage read/unread status of ActiveRecord objects - and it's fast.
|
|
18
20
|
|
19
21
|
## Requirements
|
20
22
|
|
21
|
-
* Ruby 1.9.3 or
|
23
|
+
* Ruby 1.9.3 or newer
|
22
24
|
* Rails 3 (including 3.0, 3.1, 3.2) and Rails 4. For use with Rails 2.3 there is a branch named "rails2"
|
23
25
|
* Needs a timestamp field in your models (like created_at or updated_at) with a database index on it
|
24
26
|
|
25
27
|
|
28
|
+
## Changelog
|
29
|
+
|
30
|
+
https://github.com/ledermann/unread/releases
|
31
|
+
|
32
|
+
|
26
33
|
## Installation
|
27
34
|
|
28
35
|
Step 1: Add this to your Gemfile:
|
@@ -133,4 +140,4 @@ There are two other gems/plugins doing a similar job:
|
|
133
140
|
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.
|
134
141
|
|
135
142
|
|
136
|
-
Copyright (c) 2010-
|
143
|
+
Copyright (c) 2010-2014 [Georg Ledermann](http://www.georg-ledermann.de), released under the MIT license
|
data/Rakefile
CHANGED
@@ -1,15 +1,6 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
2
3
|
|
3
|
-
|
4
|
-
require 'rake/testtask'
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
|
7
|
-
task :default => :test
|
8
|
-
|
9
|
-
desc 'Test the unread plugin.'
|
10
|
-
Rake::TestTask.new(:test) do |t|
|
11
|
-
t.libs << 'lib'
|
12
|
-
t.libs << 'test'
|
13
|
-
t.pattern = 'test/**/*_test.rb'
|
14
|
-
t.verbose = true
|
15
|
-
end
|
6
|
+
task :default => :spec
|
data/ci/Gemfile-rails-3-0
CHANGED
data/ci/Gemfile-rails-3-1
CHANGED
data/ci/Gemfile-rails-3-2
CHANGED
data/ci/Gemfile-rails-4-0
CHANGED
@@ -9,7 +9,7 @@ module Unread
|
|
9
9
|
source_root File.expand_path('../templates', __FILE__)
|
10
10
|
|
11
11
|
def create_migration_file
|
12
|
-
migration_template 'migration.rb', 'db/migrate/unread_migration'
|
12
|
+
migration_template 'migration.rb', 'db/migrate/unread_migration.rb'
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.next_migration_number(dirname)
|
data/lib/unread/readable.rb
CHANGED
@@ -2,6 +2,8 @@ module Unread
|
|
2
2
|
module Readable
|
3
3
|
module ClassMethods
|
4
4
|
def mark_as_read!(target, options)
|
5
|
+
raise ArgumentError unless options.is_a?(Hash)
|
6
|
+
|
5
7
|
user = options[:for]
|
6
8
|
assert_reader(user)
|
7
9
|
|
@@ -70,7 +72,7 @@ module Unread
|
|
70
72
|
ReadMark.transaction do
|
71
73
|
ReadMark.delete_all :readable_type => self.base_class.name
|
72
74
|
ReadMark.connection.execute <<-EOT
|
73
|
-
INSERT INTO
|
75
|
+
INSERT INTO #{ReadMark.table_name} (user_id, readable_type, timestamp)
|
74
76
|
SELECT #{ReadMark.reader_class.primary_key}, '#{self.base_class.name}', '#{Time.current.to_s(:db)}'
|
75
77
|
FROM #{ReadMark.reader_class.table_name}
|
76
78
|
EOT
|
data/lib/unread/scopes.rb
CHANGED
@@ -4,7 +4,7 @@ module Unread
|
|
4
4
|
def join_read_marks(user)
|
5
5
|
assert_reader(user)
|
6
6
|
|
7
|
-
joins "LEFT JOIN read_marks ON read_marks.readable_type = '#{base_class.name}'
|
7
|
+
joins "LEFT JOIN #{ReadMark.table_name} as read_marks ON read_marks.readable_type = '#{base_class.name}'
|
8
8
|
AND read_marks.readable_id = #{table_name}.#{primary_key}
|
9
9
|
AND read_marks.user_id = #{user.id}
|
10
10
|
AND read_marks.timestamp >= #{table_name}.#{readable_options[:on]}"
|
data/lib/unread/version.rb
CHANGED
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Unread::Base do
|
4
|
+
before :each do
|
5
|
+
@email = Email.create!
|
6
|
+
wait
|
7
|
+
@reader = Reader.create! :name => 'John'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe :acts_as_reader do
|
11
|
+
it "should create global read mark" do
|
12
|
+
expect(@reader.read_marks.count).to eq 1
|
13
|
+
expect(@reader.read_marks.global.count).to eq 1
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should define association for ReadMark" do
|
17
|
+
expect(@reader.read_marks.first.user).to eq(@reader)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should reset read_marks for created reader" do
|
21
|
+
expect(Email.unread_by(@reader)).to be_empty
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe :acts_as_readable do
|
26
|
+
it "should define association" do
|
27
|
+
expect(@email.read_marks.count).to eq 0
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should add class to ReadMark.readable_classes" do
|
31
|
+
expect(ReadMark.readable_classes).to eq [ Email ]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should use default options" do
|
35
|
+
expect(Email.readable_options).to eq({ :on => :updated_at })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/model/email.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Unread::Readable do
|
4
|
+
before :each do
|
5
|
+
@reader = Reader.create! :name => 'David'
|
6
|
+
@other_reader = Reader.create :name => 'Matz'
|
7
|
+
wait
|
8
|
+
@email1 = Email.create!
|
9
|
+
wait
|
10
|
+
@email2 = Email.create!
|
11
|
+
end
|
12
|
+
|
13
|
+
describe :unread_by do
|
14
|
+
it "should return all objects" do
|
15
|
+
expect(Email.unread_by(@reader)).to eq [@email1, @email2]
|
16
|
+
expect(Email.unread_by(@other_reader)).to eq [@email1, @email2]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return unread records" do
|
20
|
+
@email1.mark_as_read! :for => @reader
|
21
|
+
|
22
|
+
expect(Email.unread_by(@reader)).to eq [@email2]
|
23
|
+
expect(Email.unread_by(@reader).count).to eq 1
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not allow invalid parameter" do
|
27
|
+
[ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
|
28
|
+
expect {
|
29
|
+
Email.unread_by(not_a_reader)
|
30
|
+
}.to raise_error(ArgumentError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not allow unsaved reader" do
|
35
|
+
unsaved_reader = Reader.new
|
36
|
+
|
37
|
+
expect {
|
38
|
+
Email.unread_by(unsaved_reader)
|
39
|
+
}.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe :with_read_marks_for do
|
44
|
+
it "should return readables" do
|
45
|
+
expect(Email.with_read_marks_for(@reader).to_a).to eq([@email1, @email2])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be countable" do
|
49
|
+
expect(Email.with_read_marks_for(@reader).count(:messageid)).to eq(2)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not allow invalid parameter" do
|
53
|
+
[ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
|
54
|
+
expect {
|
55
|
+
Email.with_read_marks_for(not_a_reader)
|
56
|
+
}.to raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not allow unsaved reader" do
|
61
|
+
unsaved_reader = Reader.new
|
62
|
+
|
63
|
+
expect {
|
64
|
+
Email.with_read_marks_for(unsaved_reader)
|
65
|
+
}.to raise_error(ArgumentError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe :unread? do
|
70
|
+
it "should recognize unread object" do
|
71
|
+
expect(@email1.unread?(@reader)).to be_truthy
|
72
|
+
expect(@email1.unread?(@other_reader)).to be_truthy
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should handle updating object" do
|
76
|
+
@email1.mark_as_read! :for => @reader
|
77
|
+
wait
|
78
|
+
expect(@email1.unread?(@reader)).to be_falsey
|
79
|
+
|
80
|
+
@email1.update_attributes! :subject => 'changed'
|
81
|
+
expect(@email1.unread?(@reader)).to be_truthy
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should raise error for invalid argument" do
|
85
|
+
expect {
|
86
|
+
@email1.unread?(42)
|
87
|
+
}.to raise_error(ArgumentError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should work with eager-loaded read marks" do
|
91
|
+
@email1.mark_as_read! :for => @reader
|
92
|
+
|
93
|
+
expect {
|
94
|
+
emails = Email.with_read_marks_for(@reader).to_a
|
95
|
+
|
96
|
+
expect(emails[0].unread?(@reader)).to be_falsey
|
97
|
+
expect(emails[1].unread?(@reader)).to be_truthy
|
98
|
+
}.to perform_queries(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#mark_as_read!' do
|
103
|
+
it "should mark a single object as read" do
|
104
|
+
@email1.mark_as_read! :for => @reader
|
105
|
+
|
106
|
+
expect(@email1.unread?(@reader)).to be_falsey
|
107
|
+
expect(Email.unread_by(@reader)).to eq [@email2]
|
108
|
+
|
109
|
+
expect(@email1.unread?(@other_reader)).to be_truthy
|
110
|
+
expect(Email.unread_by(@other_reader)).to eq [@email1, @email2]
|
111
|
+
|
112
|
+
expect(@reader.read_marks.single.count).to eq 1
|
113
|
+
expect(@reader.read_marks.single.first.readable).to eq @email1
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should be idempotent" do
|
117
|
+
@email1.mark_as_read! :for => @reader
|
118
|
+
@email1.mark_as_read! :for => @reader
|
119
|
+
|
120
|
+
expect(@reader.read_marks.single.count).to eq 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '.mark_as_read!' do
|
125
|
+
it "should mark multi objects as read" do
|
126
|
+
expect(@email1.unread?(@reader)).to be_truthy
|
127
|
+
expect(@email2.unread?(@reader)).to be_truthy
|
128
|
+
|
129
|
+
Email.mark_as_read! [ @email1, @email2 ], :for => @reader
|
130
|
+
|
131
|
+
expect(@email1.unread?(@reader)).to be_falsey
|
132
|
+
expect(@email2.unread?(@reader)).to be_falsey
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should mark all objects as read" do
|
136
|
+
Email.mark_as_read! :all, :for => @reader
|
137
|
+
|
138
|
+
expect(@reader.read_mark_global(Email).timestamp).to eq Time.current
|
139
|
+
expect(@reader.read_marks.single).to eq []
|
140
|
+
expect(ReadMark.single.count).to eq 0
|
141
|
+
expect(ReadMark.global.count).to eq 2
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should mark all objects as read with existing read objects" do
|
145
|
+
wait
|
146
|
+
|
147
|
+
Email.mark_as_read! :all, :for => @reader
|
148
|
+
@email1.mark_as_read! :for => @reader
|
149
|
+
|
150
|
+
expect(@reader.read_marks.single).to eq []
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should not allow invalid arguments" do
|
154
|
+
expect {
|
155
|
+
Email.mark_as_read! :foo, :for => @reader
|
156
|
+
}.to raise_error(ArgumentError)
|
157
|
+
|
158
|
+
expect {
|
159
|
+
Email.mark_as_read! :foo, :bar
|
160
|
+
}.to raise_error(ArgumentError)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe :reset_read_marks_for_all do
|
165
|
+
it "should reset read marks" do
|
166
|
+
Email.reset_read_marks_for_all
|
167
|
+
|
168
|
+
expect(ReadMark.single.count).to eq 0
|
169
|
+
expect(ReadMark.global.count).to eq 2
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe :cleanup_read_marks! do
|
174
|
+
it "should delete all single read marks" do
|
175
|
+
expect(@reader.read_marks.single.count).to eq 0
|
176
|
+
|
177
|
+
@email1.mark_as_read! :for => @reader
|
178
|
+
|
179
|
+
expect(Email.unread_by(@reader)).to eq [@email2]
|
180
|
+
expect(@reader.read_marks.single.count).to eq 1
|
181
|
+
|
182
|
+
Email.cleanup_read_marks!
|
183
|
+
|
184
|
+
@reader.reload
|
185
|
+
expect(@reader.read_marks.single.count).to eq 0
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should reset if all objects are read" do
|
189
|
+
@email1.mark_as_read! :for => @reader
|
190
|
+
@email2.mark_as_read! :for => @reader
|
191
|
+
|
192
|
+
expect(@reader.read_marks.single.count).to eq 2
|
193
|
+
|
194
|
+
Email.cleanup_read_marks!
|
195
|
+
|
196
|
+
expect(@reader.read_marks.single.count).to eq 0
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should not delete read marks from other readables" do
|
200
|
+
other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
|
201
|
+
Email.cleanup_read_marks!
|
202
|
+
|
203
|
+
expect(ReadMark.exists?(other_read_mark.id)).to be_truthy
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
]
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter '/spec/'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'active_record'
|
13
|
+
require 'timecop'
|
14
|
+
require 'unread'
|
15
|
+
|
16
|
+
require 'model/reader'
|
17
|
+
require 'model/email'
|
18
|
+
|
19
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
20
|
+
# in spec/support/ and its subdirectories.
|
21
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
22
|
+
|
23
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
24
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
25
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
26
|
+
# loaded once.
|
27
|
+
#
|
28
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
29
|
+
RSpec.configure do |config|
|
30
|
+
# Run specs in random order to surface order dependencies. If you find an
|
31
|
+
# order dependency and want to debug it, you can fix the order by providing
|
32
|
+
# the seed, which is printed after each run.
|
33
|
+
# --seed 1234
|
34
|
+
# config.order = 'random'
|
35
|
+
|
36
|
+
config.before :each do
|
37
|
+
clear_db
|
38
|
+
end
|
39
|
+
|
40
|
+
config.after :each do
|
41
|
+
Timecop.return
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if I18n.respond_to?(:enforce_available_locales=)
|
46
|
+
I18n.enforce_available_locales = false
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_db
|
50
|
+
puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
51
|
+
|
52
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
|
53
|
+
ActiveRecord::Migration.verbose = false
|
54
|
+
|
55
|
+
require File.expand_path('../../lib/generators/unread/migration/templates/migration.rb', __FILE__)
|
56
|
+
UnreadMigration.migrate(:up)
|
57
|
+
|
58
|
+
ActiveRecord::Schema.define(:version => 1) do
|
59
|
+
create_table :readers, :primary_key => 'number', :force => true do |t|
|
60
|
+
t.string :name
|
61
|
+
end
|
62
|
+
|
63
|
+
create_table :emails, :primary_key => 'messageid', :force => true do |t|
|
64
|
+
t.string :subject
|
65
|
+
t.text :content
|
66
|
+
t.datetime :created_at
|
67
|
+
t.datetime :updated_at
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_db
|
73
|
+
Reader.delete_all
|
74
|
+
Email.delete_all
|
75
|
+
ReadMark.delete_all
|
76
|
+
end
|
77
|
+
|
78
|
+
setup_db
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec::Matchers.define :perform_queries do |expected|
|
2
|
+
match do |block|
|
3
|
+
query_count(&block) == expected
|
4
|
+
end
|
5
|
+
|
6
|
+
failure_message do |actual|
|
7
|
+
"Expected to run #{expected} queries, got #{@counter.query_count}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def query_count(&block)
|
11
|
+
@counter = ActiveRecord::QueryCounter.new
|
12
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', @counter.to_proc)
|
13
|
+
yield
|
14
|
+
ActiveSupport::Notifications.unsubscribe(@counter.to_proc)
|
15
|
+
|
16
|
+
@counter.query_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def supports_block_expectations?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class QueryCounter
|
3
|
+
attr_reader :query_count
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@query_count = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_proc
|
10
|
+
lambda(&method(:callback))
|
11
|
+
end
|
12
|
+
|
13
|
+
def callback(name, start, finish, message_id, values)
|
14
|
+
@query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name]) || values[:sql] =~ /^begin/i || values[:sql] =~ /^commit/i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/unread.gemspec
CHANGED
@@ -23,4 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_development_dependency 'rake'
|
24
24
|
s.add_development_dependency 'timecop'
|
25
25
|
s.add_development_dependency 'sqlite3'
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
s.add_development_dependency 'simplecov'
|
28
|
+
s.add_development_dependency 'coveralls'
|
26
29
|
end
|
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.4.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:
|
11
|
+
date: 2014-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -66,6 +66,48 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: coveralls
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
69
111
|
description: 'This gem creates a scope for unread objects and adds methods to mark
|
70
112
|
objects as read '
|
71
113
|
email:
|
@@ -80,11 +122,11 @@ files:
|
|
80
122
|
- MIT-LICENSE
|
81
123
|
- README.md
|
82
124
|
- Rakefile
|
83
|
-
- changelog.md
|
84
125
|
- ci/Gemfile-rails-3-0
|
85
126
|
- ci/Gemfile-rails-3-1
|
86
127
|
- ci/Gemfile-rails-3-2
|
87
128
|
- ci/Gemfile-rails-4-0
|
129
|
+
- ci/Gemfile-rails-4-1
|
88
130
|
- lib/generators/unread/migration/migration_generator.rb
|
89
131
|
- lib/generators/unread/migration/templates/migration.rb
|
90
132
|
- lib/unread.rb
|
@@ -94,10 +136,15 @@ files:
|
|
94
136
|
- lib/unread/reader.rb
|
95
137
|
- lib/unread/scopes.rb
|
96
138
|
- lib/unread/version.rb
|
97
|
-
-
|
98
|
-
-
|
99
|
-
-
|
100
|
-
-
|
139
|
+
- spec/base_spec.rb
|
140
|
+
- spec/model/email.rb
|
141
|
+
- spec/model/reader.rb
|
142
|
+
- spec/read_mark_spec.rb
|
143
|
+
- spec/readable_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
- spec/support/matchers/perform_queries.rb
|
146
|
+
- spec/support/query_counter.rb
|
147
|
+
- spec/support/timecop.rb
|
101
148
|
- unread.gemspec
|
102
149
|
homepage: ''
|
103
150
|
licenses: []
|
@@ -118,12 +165,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
165
|
version: '0'
|
119
166
|
requirements: []
|
120
167
|
rubyforge_project: unread
|
121
|
-
rubygems_version: 2.
|
168
|
+
rubygems_version: 2.2.2
|
122
169
|
signing_key:
|
123
170
|
specification_version: 4
|
124
171
|
summary: Manages read/unread status of ActiveRecord objects
|
125
172
|
test_files:
|
126
|
-
-
|
127
|
-
-
|
128
|
-
-
|
129
|
-
-
|
173
|
+
- spec/base_spec.rb
|
174
|
+
- spec/model/email.rb
|
175
|
+
- spec/model/reader.rb
|
176
|
+
- spec/read_mark_spec.rb
|
177
|
+
- spec/readable_spec.rb
|
178
|
+
- spec/spec_helper.rb
|
179
|
+
- spec/support/matchers/perform_queries.rb
|
180
|
+
- spec/support/query_counter.rb
|
181
|
+
- spec/support/timecop.rb
|
data/changelog.md
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
# Changelog
|
2
|
-
|
3
|
-
## 0.3.1 - 2013-12-04
|
4
|
-
|
5
|
-
* Use Time.current instead of Time.now to avoid time zone trouble. Issue #18 (thanks to @henrythe9th)
|
6
|
-
* Handle primary keys other than "id". Issue #29 (thanks to @bcavileer)
|
7
|
-
|
8
|
-
|
9
|
-
## 0.3.0 - 2013-03-17
|
10
|
-
|
11
|
-
* Support for Rails 4 (beta1)
|
12
|
-
|
13
|
-
|
14
|
-
## 0.2.0 - 2013-02-18
|
15
|
-
|
16
|
-
* Support for Rails 2 dropped
|
17
|
-
* Refactoring
|
18
|
-
* Added migration generator
|
19
|
-
|
20
|
-
|
21
|
-
## 0.1.2 - 2013-01-27
|
22
|
-
|
23
|
-
* Scopes: Improved parameter check
|
24
|
-
|
25
|
-
|
26
|
-
## 0.1.1 - 2012-05-01
|
27
|
-
|
28
|
-
* Fixed handling namespaced classes. Closes #10 (thanks to @stanislaw)
|
29
|
-
|
30
|
-
|
31
|
-
## 0.1.0 - 2012-04-21
|
32
|
-
|
33
|
-
* Added scope "with_read_marks_for"
|
34
|
-
* Fixed #7: Added attr_accessible to all ReadMark attributes (thanks to @negative)
|
35
|
-
|
36
|
-
|
37
|
-
## 0.0.7 - 2012-02-29
|
38
|
-
|
39
|
-
* Cleanup files
|
40
|
-
* acts_as_reader: Using inverse_of (available since Rails 2.3.6)
|
41
|
-
|
42
|
-
|
43
|
-
## 0.0.6 - 2011-11-11
|
44
|
-
|
45
|
-
* Fixed #5: Gemspec dependency fix (thanks to @bricker88)
|
46
|
-
* Fixed #6: Removed hard coded dependency on a class named "User" (thanks to @mixandgo)
|
47
|
-
* Some cleanup
|
48
|
-
|
49
|
-
|
50
|
-
## 0.0.5 - 2011-09-09
|
51
|
-
|
52
|
-
* Fixed class loading issue in development environment
|
53
|
-
|
54
|
-
|
55
|
-
## 0.0.4 - 2011-08-31
|
56
|
-
|
57
|
-
* Ignore multiple calls of acts_as_*
|
58
|
-
* Improved error messages
|
59
|
-
* Tested with Rails 3.1
|
60
|
-
|
61
|
-
|
62
|
-
## 0.0.3 - 2011-08-01
|
63
|
-
|
64
|
-
* Fixed gemspec by adding development dependencies
|
65
|
-
* Testing with Travis CI
|
66
|
-
|
67
|
-
|
68
|
-
## 0.0.2 - 2011-06-23
|
69
|
-
|
70
|
-
* Fixed scoping for ActiveRecord 2.x
|
71
|
-
|
72
|
-
|
73
|
-
## 0.0.1 - 2011-06-23
|
74
|
-
|
75
|
-
* Released as Gem
|
data/test/database.yml
DELETED
data/test/schema.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
ActiveRecord::Schema.define(:version => 0) do
|
2
|
-
create_table :readers, :primary_key => 'number', :force => true do |t|
|
3
|
-
t.string :name
|
4
|
-
end
|
5
|
-
|
6
|
-
create_table :emails, :primary_key => 'messageid', :force => true do |t|
|
7
|
-
t.string :subject
|
8
|
-
t.text :content
|
9
|
-
t.datetime :created_at
|
10
|
-
t.datetime :updated_at
|
11
|
-
end
|
12
|
-
|
13
|
-
create_table :read_marks, :force => true do |t|
|
14
|
-
t.integer :readable_id
|
15
|
-
t.integer :user_id, :null => false
|
16
|
-
t.string :readable_type, :null => false
|
17
|
-
t.datetime :timestamp
|
18
|
-
end
|
19
|
-
add_index :read_marks, [:user_id, :readable_type, :readable_id]
|
20
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require 'active_support'
|
3
|
-
require 'active_support/test_case'
|
4
|
-
require 'active_record'
|
5
|
-
require 'timecop'
|
6
|
-
|
7
|
-
configs = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
8
|
-
ActiveRecord::Base.configurations = configs
|
9
|
-
|
10
|
-
ActiveRecord::Base.establish_connection('sqlite')
|
11
|
-
ActiveRecord::Migration.verbose = false
|
12
|
-
load(File.dirname(__FILE__) + "/schema.rb")
|
13
|
-
|
14
|
-
require 'unread'
|
15
|
-
|
16
|
-
class Reader < ActiveRecord::Base
|
17
|
-
self.primary_key = 'number'
|
18
|
-
acts_as_reader
|
19
|
-
end
|
20
|
-
|
21
|
-
class Email < ActiveRecord::Base
|
22
|
-
self.primary_key = 'messageid'
|
23
|
-
|
24
|
-
acts_as_readable :on => :updated_at
|
25
|
-
end
|
26
|
-
|
27
|
-
puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
data/test/unread_test.rb
DELETED
@@ -1,166 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class UnreadTest < ActiveSupport::TestCase
|
4
|
-
def setup
|
5
|
-
@reader = Reader.create! :name => 'David'
|
6
|
-
@other_reader = Reader.create :name => 'Matz'
|
7
|
-
wait
|
8
|
-
@email1 = Email.create!
|
9
|
-
wait
|
10
|
-
@email2 = Email.create!
|
11
|
-
end
|
12
|
-
|
13
|
-
def teardown
|
14
|
-
Reader.delete_all
|
15
|
-
Email.delete_all
|
16
|
-
ReadMark.delete_all
|
17
|
-
Timecop.return
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_schema_has_loaded_correctly
|
21
|
-
assert_equal [@email1, @email2], Email.all
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_readable_classes
|
25
|
-
assert_equal [ Email ], ReadMark.readable_classes
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_reader_class
|
29
|
-
assert_equal Reader, ReadMark.reader_class
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_scope
|
33
|
-
assert_equal [@email1, @email2], Email.unread_by(@reader)
|
34
|
-
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
35
|
-
|
36
|
-
assert_equal 2, Email.unread_by(@reader).count
|
37
|
-
assert_equal 2, Email.unread_by(@other_reader).count
|
38
|
-
end
|
39
|
-
|
40
|
-
def test_with_read_marks_for
|
41
|
-
@email1.mark_as_read! :for => @reader
|
42
|
-
|
43
|
-
emails = Email.with_read_marks_for(@reader).to_a
|
44
|
-
|
45
|
-
assert emails[0].read_mark_id.present?
|
46
|
-
assert emails[1].read_mark_id.nil?
|
47
|
-
|
48
|
-
assert_equal false, emails[0].unread?(@reader)
|
49
|
-
assert_equal true, emails[1].unread?(@reader)
|
50
|
-
end
|
51
|
-
|
52
|
-
def test_scope_param_check
|
53
|
-
[ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
|
54
|
-
assert_raise(ArgumentError) { Email.unread_by(not_a_reader)}
|
55
|
-
assert_raise(ArgumentError) { Email.with_read_marks_for(not_a_reader)}
|
56
|
-
end
|
57
|
-
|
58
|
-
unsaved_reader = Reader.new
|
59
|
-
assert_raise(ArgumentError) { Email.unread_by(unsaved_reader)}
|
60
|
-
assert_raise(ArgumentError) { Email.with_read_marks_for(unsaved_reader)}
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_scope_after_reset
|
64
|
-
@email1.mark_as_read! :for => @reader
|
65
|
-
|
66
|
-
assert_equal [@email2], Email.unread_by(@reader)
|
67
|
-
assert_equal 1, Email.unread_by(@reader).count
|
68
|
-
end
|
69
|
-
|
70
|
-
def test_unread_after_create
|
71
|
-
assert_equal true, @email1.unread?(@reader)
|
72
|
-
assert_equal true, @email1.unread?(@other_reader)
|
73
|
-
|
74
|
-
assert_raise(ArgumentError) {
|
75
|
-
@email1.unread?(42)
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_unread_after_update
|
80
|
-
@email1.mark_as_read! :for => @reader
|
81
|
-
wait
|
82
|
-
@email1.update_attributes! :subject => 'changed'
|
83
|
-
|
84
|
-
assert_equal true, @email1.unread?(@reader)
|
85
|
-
end
|
86
|
-
|
87
|
-
def test_mark_as_read
|
88
|
-
@email1.mark_as_read! :for => @reader
|
89
|
-
|
90
|
-
assert_equal false, @email1.unread?(@reader)
|
91
|
-
assert_equal [@email2], Email.unread_by(@reader)
|
92
|
-
|
93
|
-
assert_equal true, @email1.unread?(@other_reader)
|
94
|
-
assert_equal [@email1, @email2], Email.unread_by(@other_reader)
|
95
|
-
|
96
|
-
assert_equal 1, @reader.read_marks.single.count
|
97
|
-
assert_equal @email1, @reader.read_marks.single.first.readable
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_mark_as_read_multiple
|
101
|
-
assert_equal true, @email1.unread?(@reader)
|
102
|
-
assert_equal true, @email2.unread?(@reader)
|
103
|
-
|
104
|
-
Email.mark_as_read! [ @email1, @email2 ], :for => @reader
|
105
|
-
|
106
|
-
assert_equal false, @email1.unread?(@reader)
|
107
|
-
assert_equal false, @email2.unread?(@reader)
|
108
|
-
end
|
109
|
-
|
110
|
-
def test_mark_as_read_with_marked_all
|
111
|
-
wait
|
112
|
-
|
113
|
-
Email.mark_as_read! :all, :for => @reader
|
114
|
-
@email1.mark_as_read! :for => @reader
|
115
|
-
|
116
|
-
assert_equal [], @reader.read_marks.single
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_mark_as_read_twice
|
120
|
-
@email1.mark_as_read! :for => @reader
|
121
|
-
@email1.mark_as_read! :for => @reader
|
122
|
-
|
123
|
-
assert_equal 1, @reader.read_marks.single.count
|
124
|
-
end
|
125
|
-
|
126
|
-
def test_mark_all_as_read
|
127
|
-
Email.mark_as_read! :all, :for => @reader
|
128
|
-
|
129
|
-
assert_equal Time.current, @reader.read_mark_global(Email).timestamp
|
130
|
-
assert_equal [], @reader.read_marks.single
|
131
|
-
assert_equal 0, ReadMark.single.count
|
132
|
-
assert_equal 2, ReadMark.global.count
|
133
|
-
end
|
134
|
-
|
135
|
-
def test_cleanup_read_marks
|
136
|
-
assert_equal 0, @reader.read_marks.single.count
|
137
|
-
|
138
|
-
@email1.mark_as_read! :for => @reader
|
139
|
-
|
140
|
-
assert_equal [@email2], Email.unread_by(@reader)
|
141
|
-
assert_equal 1, @reader.read_marks.single.count
|
142
|
-
|
143
|
-
Email.cleanup_read_marks!
|
144
|
-
|
145
|
-
@reader.reload
|
146
|
-
assert_equal 0, @reader.read_marks.single.count
|
147
|
-
end
|
148
|
-
|
149
|
-
def test_cleanup_read_marks_not_delete_from_other_readables
|
150
|
-
other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
|
151
|
-
Email.cleanup_read_marks!
|
152
|
-
assert_equal true, !!ReadMark.exists?(other_read_mark.id) # Rails4 does not return true, but count instead.
|
153
|
-
end
|
154
|
-
|
155
|
-
def test_reset_read_marks_for_all
|
156
|
-
Email.reset_read_marks_for_all
|
157
|
-
|
158
|
-
assert_equal 0, ReadMark.single.count
|
159
|
-
assert_equal 2, ReadMark.global.count
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
def wait
|
164
|
-
Timecop.freeze(1.minute.from_now.change(:usec => 0))
|
165
|
-
end
|
166
|
-
end
|