unread-mongoid 0.0.5 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8ad50fe8b295cea62aca80e1fc08fa149caeb5d
4
- data.tar.gz: 6612008cbf7da7497baa6591ccbfc551e6baa9d0
3
+ metadata.gz: 1cd035eab063883dddcbb6f3e99398b4dc5346d7
4
+ data.tar.gz: e18f414642a5cd38eea6fd8a55cbc2fdf2c0606e
5
5
  SHA512:
6
- metadata.gz: 64d9d12c0183503a7a5a441941cd6309b45104ee5dc7d3c68972d20ae53a67bba740ee28bc546b8783d53f13d99bbd0a7a5ef6fc6b8bd8e0e7d6da62f6b88435
7
- data.tar.gz: 2afd84b2942c203d23951e9d363c880d1ef9eefd6092845d469abc1837338197ee640a98bfea9995da8e45401a88455050684db273e8943ad53c047ce6f37834
6
+ metadata.gz: e0c977d07ba535824213afc3ac6b511120ee2cf7700aedd4f56f6596be3a2cc5c79022d835568ea29090fd364720fb1de507b33ef5208cba821968e5e6be6467
7
+ data.tar.gz: 9887a638a534ce31d9a6261f93f59b58e85f537d31ab14615f6c9434fa41a7c8402fd120e86fab5a7d38dbef0b026a897b67eebed724ce7b2720c018eca19bbb
data/README.md CHANGED
@@ -4,39 +4,16 @@ Unread-Mongoid
4
4
  Ruby gem to manage read/unread status of Mongoid objects.
5
5
 
6
6
  ## Credit
7
- First and foremost this is a fork of [Unread](https://github.com/ledermann/unread) by [Georg Ledermann](http://www.georg-ledermann.de). If you don't have to use Mongo for this I highly reccomend you use his gem, this is a task much better suited to a relational db.
7
+ First and foremost this is a fork of [Unread](https://github.com/ledermann/unread) by [Georg Ledermann](http://www.georg-ledermann.de). If you are using a relational DB, make sure to check it out.
8
8
 
9
9
  ## Features
10
10
 
11
11
  * Manages unread records for anything you want users to read (like messages, documents, comments etc.)
12
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
13
+ * Supports _mark all as read_ to mark **all** records (loops through creating a readmark for each)
14
14
  * Gives you a scope to get the unread or read records for a given user
15
15
  * Needs only one additional collection
16
16
 
17
-
18
- ## Requirements
19
-
20
- * Ruby 1.8.7 or 1.9.3 or 2.0.0
21
- * Rails 3 (including 3.0, 3.1, 3.2) and Rails 4.
22
- * Needs a timestamp field in your models (like created_at or updated_at) with a database index on it
23
-
24
-
25
- ## Installation
26
-
27
- Step 1: Add this to your Gemfile:
28
-
29
- ```ruby
30
- gem 'unread-mongoid'
31
- ```
32
-
33
- and run
34
-
35
- ```shell
36
- bundle
37
- ```
38
-
39
-
40
17
  ## Usage
41
18
 
42
19
  ```ruby
@@ -52,7 +29,7 @@ class Message
52
29
  include Mongoid::Timestamps
53
30
 
54
31
  include UnreadMongoid
55
- acts_as_readable :on => :created_at
32
+ acts_as_readable
56
33
  end
57
34
 
58
35
  message1 = Message.create!
@@ -69,8 +46,4 @@ Message.unread_by(current_user).entries
69
46
  Message.mark_as_read! :all, :for => current_user
70
47
  Message.unread_by(current_user).entries
71
48
  # => [ ]
72
-
73
- # Optional: Cleaning up unneeded markers.
74
- # Do this in a cron job once a day.
75
- Message.cleanup_read_marks!
76
49
  ```
@@ -5,31 +5,17 @@ module UnreadMongoid
5
5
 
6
6
  module Base
7
7
  def acts_as_reader
8
- ReadMark.belongs_to :user, :class_name => self.to_s
8
+ has_many :read_marks, as: :reader, dependent: :destroy
9
9
 
10
- has_many :read_marks, :dependent => :destroy, :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
19
- end
20
-
21
- include Reader::InstanceMethods
10
+ include Reader
22
11
  end
23
12
 
24
- def acts_as_readable(options={})
25
- class_attribute :readable_options
13
+ def acts_as_readable
14
+ has_many :read_marks, as: :readable, dependent: :destroy
26
15
 
27
- self.readable_options = options
28
-
29
- has_many :read_marks, :as => :readable, :dependent => :destroy
30
-
31
- ReadMark.readable_classes ||= []
32
- ReadMark.readable_classes << self unless ReadMark.readable_classes.include?(self)
16
+ before_save do |readable|
17
+ readable.mark_as_unread!
18
+ end
33
19
 
34
20
  include Readable::InstanceMethods
35
21
  extend Readable::ClassMethods
@@ -1,21 +1,9 @@
1
1
  class ReadMark
2
2
  include Mongoid::Document
3
3
 
4
- field :timestamp, type: Time
4
+ belongs_to :readable, polymorphic: true, index: true
5
+ belongs_to :reader, polymorphic: true, index: true
5
6
 
6
- belongs_to :readable, :polymorphic => true
7
- belongs_to :user
8
-
9
- validates_presence_of :user_id, :readable_type
10
-
11
- scope :global, -> { where(:readable_id => nil) }
12
- scope :single, -> { ne(readable_id: nil) }
13
- scope :older_than, -> (timestamp) { lt(timestamp: timestamp) }
14
-
15
- # Returns the class defined by acts_as_reader
16
- def self.reader_class
17
- reflect_on_all_associations(:belongs_to).find { |assoc| assoc.name == :user }.try(:klass)
18
- end
19
-
20
- class_attribute :readable_classes
7
+ validates_presence_of :reader_id, :reader_type, :readable_id, :readable_type
8
+ validates :reader_id, uniqueness: { scope: [:reader_type, :readable_id, :readable_type] }
21
9
  end
@@ -2,115 +2,39 @@ module UnreadMongoid
2
2
  module Readable
3
3
  module ClassMethods
4
4
  def mark_as_read!(target, options)
5
- user = options[:for]
6
- assert_reader(user)
5
+ reader = options[:for]
6
+ UnreadMongoid::Reader.assert_reader(reader)
7
7
 
8
- if target == :all
9
- reset_read_marks_for_user(user)
10
- elsif target.is_a?(Array)
11
- mark_array_as_read(target, user)
12
- else
13
- raise ArgumentError
14
- end
15
- end
16
-
17
- def mark_array_as_read(array, user)
18
- array.each do |obj|
19
- raise ArgumentError unless obj.is_a?(self)
20
-
21
- rm = obj.read_marks.where(user_id: user._id).first || obj.read_marks.build(user_id: user._id)
22
- rm.timestamp = obj.send(readable_options[:on])
23
- rm.save!
24
- end
25
- end
26
-
27
- # A scope with all items accessable for the given user
28
- # It's used in cleanup_read_marks! to support a filtered cleanup
29
- # Should be overriden if a user doesn't have access to all items
30
- # Default: User has access to all items and should read them all
31
- #
32
- # Example:
33
- # def Message.read_scope(user)
34
- # user.visible_messages
35
- # end
36
- def read_scope(user)
37
- self
38
- end
39
-
40
- def cleanup_read_marks!
41
- assert_reader_class
8
+ readables_to_mark = if(target == :all)
9
+ self.unread_by(reader)
10
+ else
11
+ target
12
+ end
42
13
 
43
- ReadMark.reader_class.each do |user|
44
- if oldest_timestamp = read_scope(user).unread_by(user).sort(readable_options[:on] => :asc).first.send(readable_options[:on])
45
- # There are unread items, so update the global read_mark for this user to the oldest
46
- # unread item and delete older read_marks
47
- update_read_marks_for_user(user, oldest_timestamp)
48
- else
49
- # There is no unread item, so deletes all markers and move global timestamp
50
- reset_read_marks_for_user(user)
51
- end
52
- end
53
- end
54
-
55
- def update_read_marks_for_user(user, timestamp)
56
- # Delete markers OLDER than the given timestamp
57
- user.read_marks.where(:readable_type => self.name).single.older_than(timestamp).delete_all
58
-
59
- # Change the global timestamp for this user
60
- rm = user.read_mark_global(self) || user.read_marks.build(:readable_type => self.name)
61
- rm.timestamp = timestamp - 1.second
62
- rm.save!
63
- end
14
+ self.unread_by(reader).each do |readable|
15
+ raise ArgumentError unless readable.is_a? self
64
16
 
65
- def reset_read_marks_for_all
66
- ReadMark.delete_all :readable_type => self.name
67
- ReadMark.reader_class.each do |user|
68
- ReadMark.create!(user_id: user._id, readable_type: self.name, timestamp: Time.current.to_s(:db))
17
+ readable.mark_as_read! :for => reader
69
18
  end
70
19
  end
71
-
72
- def reset_read_marks_for_user(user)
73
- assert_reader(user)
74
-
75
- ReadMark.delete_all :readable_type => self.name, :user_id => user._id
76
- ReadMark.create! :readable_type => self.name, :user_id => user._id, :timestamp => Time.now
77
- end
78
-
79
- def assert_reader(user)
80
- assert_reader_class
81
-
82
- unless user.is_a?(ReadMark.reader_class)
83
- raise ArgumentError, "Class #{user.class.name} is not registered by acts_as_reader!"
84
- end
85
-
86
- unless user._id
87
- raise ArgumentError, "The given user has no id!"
88
- end
89
- end
90
-
91
- def assert_reader_class
92
- raise RuntimeError, 'There is no class using acts_as_reader!' unless ReadMark.reader_class
93
- end
94
20
  end
95
21
 
96
22
  module InstanceMethods
97
- def unread?(user)
98
- self.class.unread_by(user).and(_id: self._id).exists?
23
+ def unread?(reader)
24
+ UnreadMongoid::Reader.assert_reader(reader)
25
+
26
+ ReadMark.where(reader: reader, readable: self).empty?
99
27
  end
100
28
 
101
29
  def mark_as_read!(options)
102
- user = options[:for]
103
- self.class.assert_reader(user)
30
+ reader = options[:for]
31
+ UnreadMongoid::Reader.assert_reader(reader)
104
32
 
105
- if unread?(user)
106
- rm = read_mark(user) || read_marks.build(:user_id => user._id)
107
- rm.timestamp = self.send(readable_options[:on]).to_s(:db)
108
- rm.save!
109
- end
33
+ ReadMark.create(reader: reader, readable: self)
110
34
  end
111
35
 
112
- def read_mark(user)
113
- read_marks.where(:user_id => user._id).first
36
+ def mark_as_unread!
37
+ self.read_marks.destroy_all
114
38
  end
115
39
  end
116
40
  end
@@ -1,14 +1,8 @@
1
1
  module UnreadMongoid
2
2
  module Reader
3
- module InstanceMethods
4
- def read_mark_global(klass)
5
- instance_var_name = "@read_mark_global_#{klass.name.gsub('::','_')}"
6
- if instance_variables.include?(instance_var_name.to_sym)
7
- instance_variable_get(instance_var_name)
8
- else # memoize
9
- obj = self.read_marks.where(:readable_type => klass.name).global.first
10
- instance_variable_set(instance_var_name, obj)
11
- end
3
+ def self.assert_reader(obj)
4
+ unless obj.kind_of?(UnreadMongoid::Reader)
5
+ raise ArgumentError, "Class #{obj.class.name} is not registered by acts_as_reader!"
12
6
  end
13
7
  end
14
8
  end
@@ -1,45 +1,25 @@
1
1
  module UnreadMongoid
2
2
  module Readable
3
3
  module Scopes
4
- # TODO rename some of these
4
+ def unread_by(reader)
5
+ UnreadMongoid::Reader.assert_reader(reader)
5
6
 
6
- def unread_by(user)
7
- self.not_in(_id: read_ids(user))
7
+ self.not_in(id: read_ids(reader))
8
8
  end
9
9
 
10
- def read_by(user)
11
- self.in(_id: read_ids(user))
12
- end
13
-
14
- private
15
- def read_marks_query(user)
16
- assert_reader(user)
10
+ def read_by(reader)
11
+ UnreadMongoid::Reader.assert_reader(reader)
17
12
 
18
- ReadMark.where(readable_type: self.name, user_id: user._id)
13
+ self.in(id: read_ids(reader))
19
14
  end
20
15
 
21
- def blanket_marks_query(user)
22
- read_marks_query(user).and(readable_id: nil).sort(timestamp: :desc)
23
- end
24
-
25
- def read_ids(user)
26
- specifically_marked_ids(user) + blanketed_ids(user)
27
- end
28
-
29
- def blanketed_ids(user)
30
- blanket = blanket_marks_query(user).first
31
-
32
- if blanket
33
- self.lte(self.readable_options[:on] => blanket.timestamp).only(:_id).map(&:_id)
34
- else
35
- []
36
- end
37
- end
38
-
39
- def specifically_marked_ids(user)
40
- read_marks_query(user).ne(readable_id: nil).select do |read_mark|
41
- read_mark.timestamp.to_i >= read_mark.readable.send(self.readable_options[:on]).to_i
42
- end.map(&:readable_id)
16
+ private
17
+ def read_ids(reader)
18
+ ReadMark.where(
19
+ reader_id: reader.id,
20
+ reader_type: reader.class.name,
21
+ readable_type: self.name
22
+ ).only(:readable_id).map(&:readable_id)
43
23
  end
44
24
  end
45
25
  end
@@ -1,3 +1,3 @@
1
1
  module UnreadMongoid
2
- VERSION = '0.0.5'
2
+ VERSION = '0.1.0'
3
3
  end
data/test/test_helper.rb CHANGED
@@ -7,7 +7,7 @@ Mongoid.load!(File.dirname(__FILE__) + '/mongoid.yml')
7
7
 
8
8
  require 'unread_mongoid'
9
9
 
10
- class Reader
10
+ class User
11
11
  include Mongoid::Document
12
12
  include UnreadMongoid
13
13
 
@@ -22,7 +22,7 @@ class Email
22
22
 
23
23
  include UnreadMongoid
24
24
 
25
- acts_as_readable :on => :updated_at
25
+ acts_as_readable
26
26
 
27
27
  field :subject, type: String
28
28
  field :content, type: String
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
- @reader = Reader.create! :name => 'David'
6
- @other_reader = Reader.create :name => 'Matz'
5
+ @reader = User.create! :name => 'David'
6
+ @other_reader = User.create :name => 'Matz'
7
7
 
8
8
  wait
9
9
  @email1 = Email.create!
@@ -12,24 +12,12 @@ class UnreadTest < ActiveSupport::TestCase
12
12
  end
13
13
 
14
14
  def teardown
15
- Reader.delete_all
15
+ User.delete_all
16
16
  Email.delete_all
17
17
  ReadMark.delete_all
18
18
  Timecop.return
19
19
  end
20
20
 
21
- def test_schema_has_loaded_correctly
22
- assert_equal [@email1, @email2], Email.all
23
- end
24
-
25
- def test_readable_classes
26
- assert_equal [ Email ], ReadMark.readable_classes
27
- end
28
-
29
- def test_reader_class
30
- assert_equal Reader, ReadMark.reader_class
31
- end
32
-
33
21
  def test_scope
34
22
  assert_equal [@email1, @email2], Email.unread_by(@reader)
35
23
  assert_equal [@email1, @email2], Email.unread_by(@other_reader)
@@ -44,36 +32,17 @@ class UnreadTest < ActiveSupport::TestCase
44
32
  assert_equal [@email1], Email.read_by(@reader).entries
45
33
  end
46
34
 
47
- #def test_with_read_marks_for
48
- #@email1.mark_as_read! :for => @reader
49
-
50
- #emails = Email.with_read_marks_for(@reader).entries
51
-
52
- #assert emails[0].read_mark_id.present?
53
- #assert emails[1].read_mark_ids.nil?
54
-
55
- #assert_equal false, emails[0].unread?(@reader)
56
- #assert_equal true, emails[1].unread?(@reader)
57
- #end
58
-
59
35
  def test_scope_param_check
60
36
  [ 42, nil, 'foo', :foo, {} ].each do |not_a_reader|
61
37
  assert_raise(ArgumentError) { Email.unread_by(not_a_reader) }
62
38
  assert_raise(ArgumentError) { Email.read_by(not_a_reader) }
63
39
  end
64
-
65
- # gonna keep this, but not really likely in mongoid
66
- unsaved_reader = Reader.new
67
- unsaved_reader._id = nil
68
-
69
- assert_raise(ArgumentError) { Email.unread_by(unsaved_reader) }
70
- assert_raise(ArgumentError) { Email.read_by(unsaved_reader) }
71
40
  end
72
41
 
73
42
  def test_scope_after_reset
74
43
  @email1.mark_as_read! :for => @reader
75
44
 
76
- assert_equal [@email2], Email.unread_by(@reader)
45
+ assert_equal [@email2], Email.unread_by(@reader).entries
77
46
  assert_equal 1, Email.unread_by(@reader).count
78
47
  end
79
48
 
@@ -102,9 +71,6 @@ class UnreadTest < ActiveSupport::TestCase
102
71
 
103
72
  assert_equal true, @email1.unread?(@other_reader)
104
73
  assert_equal [@email1, @email2], Email.unread_by(@other_reader).entries
105
-
106
- assert_equal 1, @reader.read_marks.single.count
107
- assert_equal @email1, @reader.read_marks.single.first.readable
108
74
  end
109
75
 
110
76
  def test_mark_as_read_multiple
@@ -117,64 +83,68 @@ class UnreadTest < ActiveSupport::TestCase
117
83
  assert_equal false, @email2.unread?(@reader)
118
84
  end
119
85
 
120
- def test_mark_as_read_with_marked_all
121
- wait
122
-
123
- Email.mark_as_read! :all, :for => @reader
124
- @email1.mark_as_read! :for => @reader
125
-
126
- assert_equal [], @reader.read_marks.single.entries
127
- end
128
-
129
86
  def test_mark_as_read_twice
130
87
  @email1.mark_as_read! :for => @reader
131
88
  @email1.mark_as_read! :for => @reader
132
89
 
133
- assert_equal 1, @reader.read_marks.single.count
90
+ assert_equal 1, ReadMark.where(reader: @reader).count
134
91
  end
135
92
 
136
93
  def test_mark_all_as_read
137
94
  Email.mark_as_read! :all, :for => @reader
138
95
 
139
- assert_equal Time.current, @reader.read_mark_global(Email).timestamp
140
- assert_equal [], @reader.read_marks.single
141
- assert_equal 0, ReadMark.single.count
142
- assert_equal 2, ReadMark.global.count
96
+ assert_equal [], Email.unread_by(@reader)
143
97
  end
144
98
 
145
- def test_cleanup_read_marks
146
- assert_equal 0, @reader.read_marks.single.count
99
+ def test_destroys_readmarks_when_readable_is_destroyed
100
+ @email1.mark_as_read! for: @reader
101
+
102
+ assert_equal 1, ReadMark.count
147
103
 
148
- @email1.mark_as_read! :for => @reader
104
+ @email1.destroy
149
105
 
150
- assert_equal [@email2], Email.unread_by(@reader).entries
151
- assert_equal 1, @reader.read_marks.single.count
106
+ assert_equal 0, ReadMark.count
107
+ end
152
108
 
153
- Email.cleanup_read_marks!
109
+ def test_destroys_readmarks_when_reader_is_destroyed
110
+ @email1.mark_as_read! for: @reader
111
+
112
+ assert_equal 1, ReadMark.count
113
+
114
+ @reader.destroy
154
115
 
155
- @reader.reload
156
- assert_equal 0, @reader.read_marks.single.count
116
+ assert_equal 0, ReadMark.count
157
117
  end
158
118
 
159
- def test_cleanup_read_marks_not_delete_from_other_readables
160
- other_read_mark = @reader.read_marks.create! :readable_type => 'Foo', :readable_id => 42, :timestamp => 5.years.ago
161
- Email.cleanup_read_marks!
162
- assert_equal true, ReadMark.where(_id: other_read_mark._id).exists?
119
+ def test_does_not_destroy_readable_when_readmark_is_destroyed
120
+ email_id = @email1.id
121
+
122
+ @email1.mark_as_read! for: @reader
123
+
124
+ ReadMark.destroy_all
125
+
126
+ assert_equal 0, ReadMark.count
127
+ assert_equal 1, Email.where(id: email_id).count
163
128
  end
164
129
 
165
- def test_reset_read_marks_for_all
166
- Email.reset_read_marks_for_all
130
+ def test_does_not_destroy_reader_when_readmark_is_destroyed
131
+ reader_id = @reader
132
+
133
+ @email1.mark_as_read! for: @reader
134
+
135
+ ReadMark.destroy_all
167
136
 
168
- assert_equal 0, ReadMark.single.count
169
- assert_equal 2, ReadMark.global.count
137
+ assert_equal 0, ReadMark.count
138
+ assert_equal 1, User.where(id: reader_id).count
170
139
  end
171
140
 
172
- def test_destroys_readmarks_when_readable_is_destroyed
173
- count = ReadMark.count
141
+ def test_mark_as_unread_sets_readable_back_to_unread
174
142
  @email1.mark_as_read! for: @reader
175
- assert_equal count + 1, ReadMark.count
176
- @email1.destroy
177
- assert_equal count, ReadMark.count
143
+ assert_equal false, @email1.unread?(@reader)
144
+
145
+ @email1.mark_as_unread!
146
+
147
+ assert_equal true, @email1.unread?(@reader)
178
148
  end
179
149
  private
180
150
  def wait
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unread-mongoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hunter Haydel
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-21 00:00:00.000000000 Z
12
+ date: 2014-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongoid