notificon 0.0.1

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.
@@ -0,0 +1,201 @@
1
+ # Public: Api for the Notificon gem for tracking notifications for users
2
+ #
3
+ # Examples
4
+ #
5
+ # Notificon.add_notification('sam', 'http://company.com/items/audha,
6
+ # 'Item title', 'bill', :commented, Time.now)
7
+ #
8
+ module Notificon
9
+ require "notificon/notification"
10
+ require "notificon/user_state"
11
+ require "notificon/mongo_store"
12
+ require "notificon/cache"
13
+ require "notificon/notification_store"
14
+ require "notificon/user_state_store"
15
+ require "notificon/controller"
16
+ require 'logger'
17
+
18
+ class << self
19
+ # Public: accessor method used to config the gem
20
+ #
21
+ # Yields the Notificon module
22
+ #
23
+ # Examples
24
+ #
25
+ # Notificon.setup do |config|
26
+ # config.connection_profile = 'mongo://myserver/notifications'
27
+ # end
28
+ #
29
+ # Returns nothing
30
+ def setup
31
+ yield self
32
+ end
33
+
34
+ # Public: cache to be used
35
+ attr_accessor :cache
36
+
37
+ # Public: Returns the query string parameter used to identify the notification.
38
+ # defaults to '_notificon_id' if not set
39
+ def notification_id_param
40
+ @_notification_id_param ||= '_notificon_id'
41
+ end
42
+
43
+ # Public: Returns the query string parameter used to identify the notification item.
44
+ def notification_item_id_param
45
+ "#{notification_id_param}_item"
46
+ end
47
+
48
+ # Public: Returns the query string parameter used to identify the user.
49
+ def notification_username_param
50
+ "#{notification_id_param}_user"
51
+ end
52
+
53
+ # Public: Sets the String to use for the notification_id_param
54
+ def notification_id_param=(value)
55
+ raise ArgumentError.new("value must a string or nil") unless value.nil? || value.is_a?(String)
56
+
57
+ @_notification_id_param = value
58
+ end
59
+
60
+ # Public: Returns the connection profile for the datastore. Currently only mongo
61
+ # supported. Defaults to mongodb://localhost/notificon_default if not set
62
+ def connection_profile
63
+ @_connection_profile ||= "mongodb://localhost/notificon_default"
64
+ end
65
+
66
+ # Public: Sets the String for the connection_profile
67
+ def connection_profile=(value)
68
+ raise ArgumentError.new("value must a string or nil") unless value.nil? || value.is_a?(String)
69
+
70
+ @_connection_profile = value
71
+ end
72
+
73
+ # Public: Returns the Logger currently enabled for the gem
74
+ # If none specified, will create one pointing at STDOUT
75
+ #
76
+ # Returns the Logger currently configured
77
+ def logger
78
+ @_logger ||= build_logger
79
+ end
80
+
81
+ # Public: Setter for the Logger for the gem to use. Allows host system
82
+ # loggers to be inject, eg Rails.logger
83
+ #
84
+ # value - The Logger to use
85
+ #
86
+ # Returns the assigned Logger
87
+ def logger=(value)
88
+ @_logger = value
89
+ end
90
+
91
+ # Public: Mark a notification as read
92
+ #
93
+ # id - The String identifying the notification
94
+ # read_at - The Time the notitication was read
95
+ #
96
+ # Returns nothing
97
+ def mark_notification_read(id, read_at)
98
+ raise ArgumentError.new("id must be a String") unless id.is_a? String
99
+ raise ArgumentError.new("read_at must be a Time") unless read_at.is_a? Time
100
+
101
+ notification = notification_store.mark_as_read(id, read_at)
102
+ update_user_unread_counts(notification.username)
103
+ end
104
+
105
+ # Public: Mark a notification as read
106
+ #
107
+ # username - The String identifying the user
108
+ # item_id - The String identifying the notification item
109
+ # read_at - The Time the notitication was read
110
+ #
111
+ # Returns nothing
112
+ def mark_all_read_for_item(username, item_id, read_at)
113
+ raise ArgumentError.new("username must be a String") unless username.is_a? String
114
+ raise ArgumentError.new("item_id must be a String") unless item_id.is_a? String
115
+ raise ArgumentError.new("read_at must be a Time") unless read_at.is_a? Time
116
+
117
+ notification_store.mark_all_read_for_item(username, item_id, read_at)
118
+ update_user_unread_counts(username)
119
+ end
120
+
121
+ # Public: Add a notification to the users lists
122
+ #
123
+ # username - The String identifying the user being notified
124
+ # item_url - A String url of the item notification relates to
125
+ # item_text - A String that describes the item
126
+ # actor - The String identifying the user that performed the action
127
+ # action - A Symbol representing the action that the actor performed
128
+ # occured_at - The Time the action was performed
129
+ # item_id - A String uniquely identifying the item the notification is for
130
+ #
131
+ # Returns the String id of the Notification generated
132
+ def add_notification(username, item_url, item_text, actor, action, occured_at, item_id=nil)
133
+ raise ArgumentError.new("username must be a String") unless username.is_a? String
134
+ raise ArgumentError.new("item_url must be a String") unless item_url.is_a? String
135
+ raise ArgumentError.new("item_text must be a String") unless item_text.is_a? String
136
+ raise ArgumentError.new("actor must be a String") unless actor.is_a? String
137
+ raise ArgumentError.new("action must be a Symbol") unless action.is_a? Symbol
138
+ raise ArgumentError.new("occured_at must be a Time") unless occured_at .is_a? Time
139
+
140
+ notification_store.add(Notification.new(:username => username, :item_url => item_url,
141
+ :item_text => item_text, :actor => actor, :action => action, :occured_at => occured_at, :item_id => item_id))
142
+ update_user_unread_counts(username)
143
+ end
144
+
145
+ # Public: Retrieve the most recent Notifications for user
146
+ #
147
+ # username - The String identifying the user
148
+ # limit - The Fixnum maximum number of items to return (default: 100)
149
+ #
150
+ # Returns Enumerable list of Notification elements
151
+ def get_notifications(username, limit=100)
152
+ raise ArgumentError.new("username must be a String") unless username.is_a? String
153
+ raise ArgumentError.new("limit must be a Fixnum") unless limit.is_a? Fixnum
154
+
155
+ notification_store.get_for_user(username, limit)
156
+ end
157
+
158
+ # Public: Retrieve the known state of the user, ie notifcation count
159
+ #
160
+ # username - The String identifying the user
161
+ #
162
+ # Returns UserState object describing the state
163
+ def get_user_state(username)
164
+ raise ArgumentError.new("username must be a String") unless username.is_a? String
165
+
166
+ user_state_store.get(username)
167
+ end
168
+
169
+ # Public: Clear all unread notifications for the user
170
+ #
171
+ # username - The String identifying the user
172
+ #
173
+ # Returns nothing
174
+ def clear_notifications(username)
175
+ raise ArgumentError.new("username must be a String") unless username.is_a? String
176
+
177
+ notification_store.mark_all_read_for_user(username, Time.now)
178
+ user_state_store.clear_notifications(username)
179
+ end
180
+
181
+ def update_user_unread_counts(username)
182
+ user_state_store.set_notifications(username, notification_store.unread_count_for_user(username))
183
+ end
184
+
185
+ private
186
+
187
+ def build_logger
188
+ logger = Logger.new(STDOUT)
189
+ logger.progname = "Notificon"
190
+ logger
191
+ end
192
+
193
+ def notification_store
194
+ @_notification_store ||= NotificationStore.new
195
+ end
196
+
197
+ def user_state_store
198
+ @_user_states_store ||= UserStateStore.new
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,23 @@
1
+ # Private: wrapper for the assigned cache
2
+ #
3
+ module Notificon
4
+ module Cache
5
+
6
+ def cache_enabled?
7
+ Notificon.cache
8
+ end
9
+
10
+ def cache_fetch(key)
11
+ if cache_enabled?
12
+ Notificon.cache.fetch("notificon_#{key}") { yield }
13
+ else
14
+ yield
15
+ end
16
+ end
17
+
18
+ def cache_delete(key)
19
+ Notificon.cache.delete("notificon_#{key}") if cache_enabled?
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,44 @@
1
+ # Public : Module to include in ApplicationController to support notification read tracking
2
+ #
3
+ # class ApplicationController
4
+ # include Notificon::Controller
5
+ # before_filter :notificon_tracker
6
+ #
7
+ # end
8
+ module Notificon
9
+ module Controller
10
+ # Public : method to use as a before filter on the application controller to track read status
11
+ # of notifications
12
+ #
13
+ # Returns bookean indicating whether updates made
14
+ def notificon_tracker
15
+ request.get? && (_track_explicit_read | _track_implicit_read)
16
+ rescue => e
17
+ Notificon.logger.error { "Notificon::Controller#notificon_tracker error processing notification params - #{params.inspect} error - #{e.inspect}" }
18
+ end
19
+
20
+ # Private : uses notificon explicit parameters to track reads for items
21
+ def _track_explicit_read
22
+ if params[Notificon.notification_item_id_param] && params[Notificon.notification_username_param]
23
+ Notificon.mark_all_read_for_item(params[Notificon.notification_username_param], params[Notificon.notification_item_id_param], _notificon_read_at)
24
+ true
25
+ elsif id = params[Notificon.notification_id_param]
26
+ Notificon.mark_notification_read(id, _notificon_read_at)
27
+ true
28
+ end
29
+ end
30
+
31
+ # Private : uses controller methods to track implicit reads for items
32
+ def _track_implicit_read
33
+ if respond_to?(:current_username) && respond_to?(:current_item_id) && current_username && current_item_id
34
+ Notificon.mark_all_read_for_item(current_username, current_item_id, _notificon_read_at)
35
+ true
36
+ end
37
+ end
38
+
39
+ # Private : lazy assignment of read_at time
40
+ def _notificon_read_at
41
+ @_notificon_read_at ||= ((respond_to?(:current_time) && current_time) || Time.now)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ # Base module for interacting with the mongo store used for persistence
2
+
3
+ require 'mongo'
4
+ require 'uri'
5
+
6
+ module Notificon
7
+ module MongoStore
8
+ include Mongo
9
+
10
+ def self.included(klass)
11
+ klass.class_eval do
12
+ extend ClassMethods
13
+ end
14
+ end
15
+
16
+ def open_connection
17
+ @db ||= open_store
18
+ end
19
+
20
+ def open_store
21
+ uri = URI.parse(Notificon.connection_profile)
22
+ Connection.from_uri(Notificon.connection_profile).db(uri.path.gsub(/^\//, ''))
23
+ end
24
+
25
+ def collection
26
+ @_collection ||= open_connection[self.class.collection_name]
27
+ end
28
+
29
+ def drop_collection
30
+ collection.drop
31
+ @_collection = nil
32
+ end
33
+
34
+ def ensure_indexes
35
+ self.class.indexes.each do |index| collection.ensure_index(index) end
36
+ end
37
+
38
+ module ClassMethods
39
+ def set_collection_name(name)
40
+ @_collection_name = name
41
+ end
42
+ def collection_name
43
+ @_collection_name
44
+ end
45
+ def set_indexes(indexes)
46
+ @_indexes = indexes
47
+ end
48
+ def indexes
49
+ @_indexes || []
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ # Class describing notification objects
2
+
3
+ require 'addressable/uri'
4
+
5
+ module Notificon
6
+ class Notification
7
+ attr_accessor :id, :username, :item_url, :item_text, :actor, :action, :occured_at, :read_at, :item_id
8
+
9
+ def initialize(attrs={})
10
+ attrs.each_pair do |k,v| send("#{k}=", v) end
11
+ end
12
+
13
+ # provided to enable simple creation from mongo hash objects
14
+ def _id=(value)
15
+ @id = value.to_s
16
+ end
17
+
18
+ def read?
19
+ !read_at.nil?
20
+ end
21
+
22
+ def url
23
+ uri = Addressable::URI.parse(item_url)
24
+ params = {Notificon.notification_id_param => id}
25
+ if item_id
26
+ params[Notificon.notification_item_id_param] = item_id
27
+ params[Notificon.notification_username_param] = username
28
+ end
29
+ uri.query_values = (uri.query_values || {}).merge(params)
30
+ uri.to_s
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,46 @@
1
+ # Private: Datamapper for Notification object
2
+ module Notificon
3
+ class NotificationStore
4
+ include MongoStore
5
+
6
+ set_collection_name :notificon_notifications
7
+ set_indexes [
8
+ [['username'], ['occured_at', Mongo::DESCENDING]]
9
+ ]
10
+
11
+ def add(notification)
12
+ collection.insert('username' => notification.username, 'item_url' => notification.item_url,
13
+ 'item_text' => notification.item_text, 'actor' => notification.actor, 'action' => notification.action,
14
+ 'occured_at' => notification.occured_at && notification.occured_at.utc, 'item_id' => notification.item_id).to_s
15
+ end
16
+
17
+ def get(id)
18
+ if item_hash = collection.find_one('_id' => BSON::ObjectId.from_string(id))
19
+ Notification.new(item_hash)
20
+ end
21
+ end
22
+
23
+ def get_for_user(username, limit)
24
+ collection.find({'username' => username}, { :limit => limit, :sort => [['occured_at', :descending]]}).collect { |item_hash|
25
+ Notification.new(item_hash)
26
+ }
27
+ end
28
+
29
+ def unread_count_for_user(username)
30
+ collection.count(:query => {'username' => username, 'read_at' => { '$exists' => false }})
31
+ end
32
+
33
+ def mark_as_read(id, read_at)
34
+ collection.update({'_id' => BSON::ObjectId.from_string(id)}, { '$set' => { 'read_at' => read_at.utc } })
35
+ get(id)
36
+ end
37
+
38
+ def mark_all_read_for_user(username, read_at)
39
+ collection.update({'username' => username, 'read_at' => { '$exists' => false } }, { '$set' => { 'read_at' => read_at.utc}}, { :multi => true})
40
+ end
41
+
42
+ def mark_all_read_for_item(username, item_id, read_at)
43
+ collection.update({'username' => username, 'item_id' => item_id, 'read_at' => { '$exists' => false } }, { '$set' => { 'read_at' => read_at.utc}}, { :multi => true})
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ # Public: Encapsulates the user state
2
+ module Notificon
3
+ class UserState
4
+
5
+ # Public : Gets the username
6
+ attr_reader :username
7
+
8
+ # Public : Gets the number of unread notifications
9
+ def notifications
10
+ @_notifications ||= 0
11
+ end
12
+
13
+ def initialize(attrs)
14
+ @username = attrs['username']
15
+ @_notifications = attrs['notifications']
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ # Private: Datamapper for Notification object
2
+ module Notificon
3
+ class UserStateStore
4
+ include MongoStore
5
+ include Cache
6
+
7
+ set_collection_name :notificon_user_state
8
+ set_indexes [
9
+ [['username']]
10
+ ]
11
+
12
+ def get(username)
13
+ if item_hash = cache_fetch("user_state_#{username}") { collection.find_one('username' => username) }
14
+ UserState.new(item_hash)
15
+ end
16
+ end
17
+
18
+ def set_notifications(username, notifications)
19
+ collection.update({'username' => username}, { '$set' => { 'notifications' => notifications }}, { :upsert => true } )
20
+ delete_cached(username)
21
+ end
22
+
23
+ def clear_notifications(username)
24
+ collection.update({'username' => username}, { '$set' => { 'notifications' => 0 } }, { :upsert => true })
25
+ delete_cached(username)
26
+ end
27
+
28
+ def delete_cached(username)
29
+ cache_delete("user_state_#{username}")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Notificon
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyController
4
+ include Notificon::Controller
5
+
6
+ def params
7
+ @_params ||= {}
8
+ end
9
+ end
10
+
11
+ describe Controller do
12
+ describe "notificon_tracker" do
13
+ before(:each) do
14
+ @controller = DummyController.new
15
+ @time = random_time
16
+ @controller.stub(:current_time) { @time }
17
+ @controller.stub(:request) { mock("Request", :get? => true) }
18
+ end
19
+
20
+ subject { @controller.notificon_tracker }
21
+
22
+ context "when id param passed" do
23
+ before(:each) do
24
+ @id = random_string
25
+ @controller.params[Notificon.notification_id_param] = @id
26
+ end
27
+
28
+ it "marks the notification as read" do
29
+ Notificon.should_receive(:mark_notification_read).with(@id, @time)
30
+ subject
31
+ end
32
+
33
+ context "when item_id param passed" do
34
+ before(:each) do
35
+ @item_id = random_string
36
+ @username = random_string
37
+ @controller.params[Notificon.notification_item_id_param] = @item_id
38
+ @controller.params[Notificon.notification_username_param] = @username
39
+ end
40
+ it "does not attempt to mark notification" do
41
+ Notificon.should_not_receive(:mark_notification_read)
42
+ subject
43
+ end
44
+ it "marks all the notifications for the item read" do
45
+ Notificon.should_receive(:mark_all_read_for_item).with(@username, @item_id, @time)
46
+ subject
47
+ end
48
+ end
49
+ end
50
+
51
+ context "when id param not passed" do
52
+ it "does not attempt to mark notification" do
53
+ Notificon.should_not_receive(:mark_notification_read)
54
+ subject
55
+ end
56
+ end
57
+
58
+ context "when controller supports current_username and current_item_id" do
59
+ before(:each) do
60
+ @current_username = random_string
61
+ @current_item_id = random_string
62
+ @controller.stub(:current_username) { @current_username }
63
+ @controller.stub(:current_item_id) { @current_item_id }
64
+ end
65
+ it "marks all the notifications for the current_item_id read" do
66
+ Notificon.should_receive(:mark_all_read_for_item).with(@current_username, @current_item_id, @time)
67
+ subject
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'addressable/uri'
3
+
4
+ describe Notification do
5
+ describe "#_id=" do
6
+ before(:each) do
7
+ @notification = Notification.new
8
+ @id = random_string
9
+ end
10
+
11
+ subject { @notification._id = @id }
12
+
13
+ it "sets the id property" do
14
+ subject
15
+ @notification.id.should eq(@id)
16
+ end
17
+ end
18
+
19
+ describe "#read?" do
20
+ before(:each) do
21
+ @notification = Notification.new
22
+ end
23
+
24
+ subject { @notification.read? }
25
+
26
+ context "when read_at is nil" do
27
+ it "should be false" do
28
+ subject.should be_false
29
+ end
30
+ end
31
+
32
+ context "when read_at has a value" do
33
+ before(:each) do
34
+ @notification.read_at = random_time
35
+ end
36
+ it "should be true" do
37
+ subject.should be_true
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#url" do
43
+ before(:each) do
44
+ @id = random_string
45
+ @item_id = nil
46
+ @username = random_string
47
+ end
48
+
49
+ subject { Notification.new(:id => @id, :item_url => @item_url, :item_id => @item_id, :username => @username).url }
50
+
51
+ context "url with query string param" do
52
+ before(:each) do
53
+ @param = random_string
54
+ @value = random_string
55
+ @item_url = "http://#{random_string}.com?#{@param}=#{@value}"
56
+ end
57
+ it "should return url with notification param appended" do
58
+ # having to parse result to cope with order of query string params
59
+ uri = Addressable::URI.parse(subject)
60
+ uri.query_values.should eq({ @param => @value, Notificon.notification_id_param => @id })
61
+ end
62
+ end
63
+
64
+ context "url without query string param" do
65
+ before(:each) do
66
+ @item_url = "http://#{random_string}.co.uk"
67
+ end
68
+ it "should return url with notification param appended" do
69
+ subject.should eq("#{@item_url}?#{Notificon.notification_id_param}=#{@id}")
70
+ end
71
+ end
72
+
73
+ context "path with anchor" do
74
+ before(:each) do
75
+ @item_url = "/clubs/4f8ea8bb3d99cf0001000008/posts/4f995996cc1db30001000001#4f9fa6f673a9650001000009"
76
+ end
77
+ it "should return url" do
78
+ subject.should eq("/clubs/4f8ea8bb3d99cf0001000008/posts/4f995996cc1db30001000001?#{Notificon.notification_id_param}=#{@id}#4f9fa6f673a9650001000009")
79
+ end
80
+ end
81
+
82
+ context "when item_id present" do
83
+ before(:each) do
84
+ @item_url = "http://#{random_string}.co.uk"
85
+ @item_id = random_string
86
+ end
87
+ it "returns the url with both item_id and username" do
88
+ subject.should eq("#{@item_url}?#{Notificon.notification_id_param}=#{@id}&#{Notificon.notification_item_id_param}=#{@item_id}&#{Notificon.notification_username_param}=#{@username}")
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+
3
+ describe NotificationStore do
4
+ before(:each) do
5
+ @store = NotificationStore.new
6
+ @store.drop_collection
7
+ end
8
+ describe "#add" do
9
+ before(:each) do
10
+ @username = random_string
11
+ @item_text = random_string
12
+ @text = random_string
13
+ @actor = random_string
14
+ @action = random_string
15
+ @occured_at = random_time
16
+ @item_id = random_string
17
+ @notification = Notification.new(:username => @username, :item_url => @item_url,
18
+ :item_text => @item_text, :actor => @actor, :action => @action, :occured_at => @occured_at, :item_id => @item_id)
19
+ end
20
+
21
+ subject { @store.add(@notification) }
22
+
23
+ it "creates a record with username set" do
24
+ @store.get(subject).username.should eq(@username)
25
+ end
26
+ it "creates a record with actor set" do
27
+ @store.get(subject).actor.should eq(@actor)
28
+ end
29
+ it "creates a record with action set" do
30
+ @store.get(subject).action.should eq(@action)
31
+ end
32
+ it "creates a record with occured_at set" do
33
+ @store.get(subject).occured_at.to_i.should eq(@occured_at.to_i)
34
+ end
35
+ it "creates a record with item_url set" do
36
+ @store.get(subject).item_url.should eq(@item_url)
37
+ end
38
+ it "creates a record with item_text set" do
39
+ @store.get(subject).item_text.should eq(@item_text)
40
+ end
41
+ it "creates a record with item_id set" do
42
+ @store.get(subject).item_id.should eq(@item_id)
43
+ end
44
+ end
45
+
46
+ describe "#get_for_user" do
47
+ before(:each) do
48
+ @username = random_string
49
+
50
+ @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
51
+ @store.add(Notification.new(:username => random_string, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
52
+ @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
53
+ end
54
+
55
+ subject { @store.get_for_user(@username, 100) }
56
+
57
+ it "returns 2 records" do
58
+ subject.count.should eq(2)
59
+ end
60
+ end
61
+
62
+ describe "#unread_count_for_user" do
63
+ before(:each) do
64
+ @username = random_string
65
+
66
+ @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
67
+ id = @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
68
+ @store.add(Notification.new(:username => random_string, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
69
+ @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
70
+ @store.mark_as_read(id, random_time)
71
+ end
72
+
73
+ subject { @store.unread_count_for_user(@username) }
74
+
75
+ it "returns 2" do
76
+ subject.should eq(2)
77
+ end
78
+ end
79
+
80
+ describe "#mark_as_read" do
81
+ before(:each) do
82
+ @id = @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
83
+ @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
84
+ @read_at = random_time
85
+ end
86
+
87
+ subject { @store.mark_as_read(@id, @read_at) }
88
+
89
+ it "marks the record as read_at" do
90
+ subject
91
+ @store.get(@id).read_at.to_i.should eq(@read_at.utc.to_i)
92
+ end
93
+ it "returns the Notification" do
94
+ subject.id.should eq(@id)
95
+ end
96
+ end
97
+
98
+ describe "#mark_all_read_for_user" do
99
+ before(:each) do
100
+ @first_id = @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
101
+ @first_read_at = random_time
102
+ @store.mark_as_read(@first_id, @first_read_at)
103
+
104
+ @second_id = @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
105
+
106
+ @third_id = @store.add(Notification.new(:username => random_string, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
107
+
108
+ @fourth_id = @store.add(Notification.new(:username => @username, :item_url => random_string, :item_text => random_string, :actor => random_string, :occured_at => random_time))
109
+
110
+ @read_at = random_time
111
+ end
112
+
113
+ subject { @store.mark_all_read_for_user(@username, @read_at) }
114
+
115
+ it "leaves the first record as already read" do
116
+ subject
117
+ @store.get(@first_id).read_at.to_i.should eq(@first_read_at.to_i)
118
+ end
119
+ it "updates the second record as not read" do
120
+ subject
121
+ @store.get(@second_id).read_at.to_i.should eq(@read_at.to_i)
122
+ end
123
+ it "leaves the third record as not for the user" do
124
+ subject
125
+ @store.get(@third_id).read_at.should be_nil
126
+ end
127
+ it "updates the fourth record as not read" do
128
+ subject
129
+ @store.get(@fourth_id).read_at.to_i.should eq(@read_at.to_i)
130
+ end
131
+ end
132
+
133
+ describe "#mark_all_read_for_item" do
134
+ before(:each) do
135
+ @item_id = random_string
136
+ @first_id = @store.add(Notification.new(:username => @username, :item_url => random_string,
137
+ :item_text => random_string, :actor => random_string, :occured_at => random_time, :item_id => @item_id))
138
+ @first_read_at = random_time
139
+ @store.mark_as_read(@first_id, @first_read_at)
140
+
141
+ @second_id = @store.add(Notification.new(:username => @username, :item_url => random_string,
142
+ :item_text => random_string, :actor => random_string, :occured_at => random_time, :item_id => @item_id))
143
+
144
+ @third_id = @store.add(Notification.new(:username => random_string, :item_url => random_string,
145
+ :item_text => random_string, :actor => random_string, :occured_at => random_time, :item_id => @item_id))
146
+
147
+ @fourth_id = @store.add(Notification.new(:username => @username, :item_url => random_string,
148
+ :item_text => random_string, :actor => random_string, :occured_at => random_time, :item_id => random_string))
149
+
150
+ @read_at = random_time
151
+ end
152
+
153
+ subject { @store.mark_all_read_for_item(@username, @item_id, @read_at) }
154
+
155
+ it "leaves the first record as already read" do
156
+ subject
157
+ @store.get(@first_id).read_at.to_i.should eq(@first_read_at.to_i)
158
+ end
159
+ it "updates the second record as not read" do
160
+ subject
161
+ @store.get(@second_id).read_at.to_i.should eq(@read_at.to_i)
162
+ end
163
+ it "leaves the third record as not for the user" do
164
+ subject
165
+ @store.get(@third_id).read_at.should be_nil
166
+ end
167
+ it "leaves the fourth record as not for the item" do
168
+ subject
169
+ @store.get(@fourth_id).read_at.should be_nil
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe UserStateStore do
4
+ before(:each) do
5
+ @store = UserStateStore.new
6
+ @store.drop_collection
7
+ @username = random_string
8
+ @store.set_notifications(@username, 2)
9
+ end
10
+
11
+ describe "#clear_notifications" do
12
+
13
+ subject { @store.clear_notifications(@username) }
14
+
15
+ it "should set the notifications to zero" do
16
+ subject
17
+ @store.get(@username).notifications.should eq(0)
18
+ end
19
+ end
20
+
21
+ describe "#set_notifications" do
22
+ before(:each) do
23
+ @count = random_integer
24
+ end
25
+
26
+ subject { @store.set_notifications(@username, @count) }
27
+
28
+ it "should set the notifications count" do
29
+ subject
30
+ @store.get(@username).notifications.should eq(@count)
31
+ end
32
+ it "should clear the cache" do
33
+ @store.should_receive(:cache_delete).with("user_state_#{@username}")
34
+ subject
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Notificon do
4
+ before(:each) do
5
+ @notification = Notification.new(:id => @id = random_string, :username => @username = random_string)
6
+ Notification.stub(:new) { @notification }
7
+
8
+ @unread_count = random_integer
9
+ @notification_store = mock(NotificationStore, :add => true, :unread_count_for_user => @unread_count, :mark_as_read => @notification)
10
+ Notificon.stub(:notification_store) { @notification_store }
11
+ @user_state_store = mock(UserStateStore, :set_notifications => true)
12
+ Notificon.stub(:user_state_store) { @user_state_store }
13
+ end
14
+ describe ".add_notification" do
15
+ before(:each) do
16
+ @item_url = random_string
17
+ @item_text = random_string
18
+ @actor = random_string
19
+ @action = random_string.to_sym
20
+ @occured_at = random_time
21
+ end
22
+
23
+ subject { Notificon.add_notification(@username, @item_url, @item_text, @actor, @action, @occured_at) }
24
+
25
+ it "creates a notification" do
26
+ Notification.should_receive(:new).with(:username => @username, :item_url => @item_url,
27
+ :item_text => @item_text, :actor => @actor, :action => @action, :occured_at => @occured_at, :item_id => nil)
28
+ subject
29
+ end
30
+ it "adds it to the notification store" do
31
+ @notification_store.should_receive(:add).with(@notification)
32
+ subject
33
+ end
34
+ it "updates the unread count for the user" do
35
+ @user_state_store.should_receive(:set_notifications).with(@username, @unread_count)
36
+ subject
37
+ end
38
+ context "when item id passed" do
39
+ before(:each) do
40
+ @item_id = random_string
41
+ end
42
+
43
+ subject { Notificon.add_notification(@username, @item_url, @item_text, @actor, @action, @occured_at, @item_id) }
44
+
45
+ it "should create the notification with the item_id" do
46
+ Notification.should_receive(:new).with(:username => @username, :item_url => @item_url,
47
+ :item_text => @item_text, :actor => @actor, :action => @action, :occured_at => @occured_at, :item_id => @item_id)
48
+ subject
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#mark_notification_read" do
54
+ before(:each) do
55
+ @read_at = random_time
56
+ end
57
+
58
+ subject { Notificon.mark_notification_read(@id, @read_at) }
59
+
60
+ it "marks the notification as read" do
61
+ @notification_store.should_receive(:mark_as_read).with(@id, @read_at)
62
+ subject
63
+ end
64
+ it "updates the unread count for the user" do
65
+ @user_state_store.should_receive(:set_notifications).with(@username, @unread_count)
66
+ subject
67
+ end
68
+ end
69
+
70
+ describe "#update_user_unread_counts" do
71
+
72
+ subject { Notificon.update_user_unread_counts(@username) }
73
+
74
+ it "gets the unread count for the user" do
75
+ @notification_store.should_receive(:unread_count_for_user).with(@username)
76
+ subject
77
+ end
78
+ it "sets the value on user state store" do
79
+ @user_state_store.should_receive(:set_notifications).with(@username, @unread_count)
80
+ subject
81
+ end
82
+ end
83
+
84
+ describe "#mark_all_read_for_item" do
85
+ before(:each) do
86
+ @username = random_string
87
+ @item_id = random_string
88
+ @read_at = random_time
89
+ @notification_store.stub(:mark_all_read_for_item)
90
+ end
91
+
92
+ subject { Notificon.mark_all_read_for_item(@username, @item_id, @read_at) }
93
+
94
+ it "marks all notifications for item id" do
95
+ @notification_store.should_receive(:mark_all_read_for_item).with(@username, @item_id, @read_at)
96
+ subject
97
+ end
98
+ it "updates the unread count for the user" do
99
+ @user_state_store.should_receive(:set_notifications).with(@username, @unread_count)
100
+ subject
101
+ end
102
+ end
103
+
104
+ describe ".clear_notifications" do
105
+ before(:each) do
106
+ @username = random_string
107
+ @notification_store.stub(:mark_all_read_for_user)
108
+ @user_state_store.stub(:clear_notifications)
109
+ end
110
+
111
+ subject { Notificon.clear_notifications(@username) }
112
+
113
+ it "marks all notifications for user as read" do
114
+ @notification_store.should_receive(:mark_all_read_for_user).with(@username, an_instance_of(Time))
115
+ subject
116
+ end
117
+ it "updates the unread count for the user" do
118
+ @user_state_store.should_receive(:clear_notifications).with(@username)
119
+ subject
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,26 @@
1
+ require 'rake'
2
+ require 'rspec'
3
+ require "#{Rake.application.original_dir}/lib/notificon"
4
+
5
+ include Notificon
6
+
7
+ RSpec.configure do |config|
8
+ # Use color in STDOUT
9
+ config.color_enabled = true
10
+ end
11
+
12
+ def random_string
13
+ (0...24).map{ ('a'..'z').to_a[rand(26)] }.join
14
+ end
15
+
16
+ def random_integer
17
+ rand(9999)
18
+ end
19
+
20
+ def random_time
21
+ Time.now - random_integer
22
+ end
23
+
24
+ def random_object_id
25
+ BSON::ObjectId.from_time(random_time).to_s
26
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: notificon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Bird
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongo
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.6'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bson_ext
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.6'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.6'
46
+ - !ruby/object:Gem::Dependency
47
+ name: addressable
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ description: Gem for tracking and managing application notifications
63
+ email: adam.bird@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - lib/notificon/cache.rb
69
+ - lib/notificon/controller.rb
70
+ - lib/notificon/mongo_store.rb
71
+ - lib/notificon/notification.rb
72
+ - lib/notificon/notification_store.rb
73
+ - lib/notificon/user_state.rb
74
+ - lib/notificon/user_state_store.rb
75
+ - lib/notificon/version.rb
76
+ - lib/notificon.rb
77
+ - spec/notificon/controller_spec.rb
78
+ - spec/notificon/notification_spec.rb
79
+ - spec/notificon/notification_store_spec.rb
80
+ - spec/notificon/user_state_store_spec.rb
81
+ - spec/notificon_spec.rb
82
+ - spec/spec_helper.rb
83
+ homepage: http://github.com/adambird/notificon
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.24
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Gem for tracking and managing application notifications
107
+ test_files:
108
+ - spec/notificon/controller_spec.rb
109
+ - spec/notificon/notification_spec.rb
110
+ - spec/notificon/notification_store_spec.rb
111
+ - spec/notificon/user_state_store_spec.rb
112
+ - spec/notificon_spec.rb
113
+ - spec/spec_helper.rb