notificon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/notificon.rb +201 -0
- data/lib/notificon/cache.rb +23 -0
- data/lib/notificon/controller.rb +44 -0
- data/lib/notificon/mongo_store.rb +53 -0
- data/lib/notificon/notification.rb +33 -0
- data/lib/notificon/notification_store.rb +46 -0
- data/lib/notificon/user_state.rb +18 -0
- data/lib/notificon/user_state_store.rb +32 -0
- data/lib/notificon/version.rb +3 -0
- data/spec/notificon/controller_spec.rb +71 -0
- data/spec/notificon/notification_spec.rb +92 -0
- data/spec/notificon/notification_store_spec.rb +172 -0
- data/spec/notificon/user_state_store_spec.rb +37 -0
- data/spec/notificon_spec.rb +122 -0
- data/spec/spec_helper.rb +26 -0
- metadata +113 -0
data/lib/notificon.rb
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|