notificon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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