notably 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +251 -0
- data/Rakefile +1 -0
- data/lib/notably.rb +14 -0
- data/lib/notably/configuration.rb +12 -0
- data/lib/notably/notifiable.rb +46 -0
- data/lib/notably/notification.rb +136 -0
- data/lib/notably/version.rb +3 -0
- data/notably.gemspec +23 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bd247eba7aa38faf56a73e9eb6bb8734d22e4358
|
4
|
+
data.tar.gz: 739b8d7bb0d16402b478e2c79d3c30e1d3822813
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc524ed6ef36a6fe5f56ded1cc91b58011603426af5e9876ec6dc0fd33d35dcea1945d4d86bf6321b3a7cc61a6c8b55c03857823b892913209513b57fd8e8a4e
|
7
|
+
data.tar.gz: fcb35392ccaf761b3a6a666a1752668e5114621539fc578c7dfe64ea34b40e0ea8a684c8b1248d8c5809d66eae2ce1dd6efa8c8fb418cc280a17f9d8c5c9e8d4
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 UpTrending LLC
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# Notably
|
2
|
+
|
3
|
+
Notably is a redis-backed notification system.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'notably'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ gem install notably
|
23
|
+
```
|
24
|
+
|
25
|
+
## Concepts
|
26
|
+
|
27
|
+
Before we dive right in to usage, let me quickly go over a few of the basic concepts of Notably.
|
28
|
+
|
29
|
+
### Notification
|
30
|
+
|
31
|
+
Most simply, we have a Notification module which you can include in your own classes. It expects a few methods to be overridden, and provides a few helper methods.
|
32
|
+
|
33
|
+
Notifications have required attributes, which you set. For instance a CommentNotification might have required attributes of `:comment_id` and `:author_id`. Those attributes become accessor methods that you can use inside your class. Then you must define two methods: `to_html` and `receivers`.
|
34
|
+
|
35
|
+
`to_html` should return an html string, which is what you will use in the view to display the notification to the user. (If you're using Rails, you'll have access to all the standard view helpers)
|
36
|
+
|
37
|
+
`receivers` should return an array of models to be notified by this notification. All the models returned should be of a class that includes the `Notably::Notifiable` module. Speaking of...
|
38
|
+
|
39
|
+
### Notifiable
|
40
|
+
|
41
|
+
The Notifiable module is what you include in the classes that should be notified of things. The only demand placed on the class that includes it is that it responds to and returns a unique value for `id`. So if you're including it in an `ActiveRecord::Base` subclass, you should be good to go.
|
42
|
+
|
43
|
+
Notifiable adds these public methods to your class:
|
44
|
+
|
45
|
+
* `notifications` Return an array of all notifications
|
46
|
+
* `notifications_since(time)` Return an array of all notifications that happened after the time parameter
|
47
|
+
* `unread_notifications` Return an array of all unread notifications
|
48
|
+
* `unread_notifications!` Return an array of all unread notifications, and update the last_notification_read_at time atomically
|
49
|
+
* `read_notifications` Return an array of all read notifications
|
50
|
+
* `read_notifications!` Update the last_notification_read_at time
|
51
|
+
* `last_notification_read_at` Return an integer representing the time the last notification was read
|
52
|
+
* `notification_key` The key in redis where the notifications will get stored
|
53
|
+
* `last_notification_read_at_key` The key in redis where the last_notification_read_at will get stored
|
54
|
+
|
55
|
+
For most setups, you should probably only really need access to three or so of those methods.
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
Lets try implementing a comment notification in a sample Rails app with a User model and a Comment model. We'll start from here:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# app/models/user.rb
|
63
|
+
class User < ActiveRecord::Base
|
64
|
+
has_many :comments, foreign_key: :author_id
|
65
|
+
end
|
66
|
+
|
67
|
+
# app/models/comment.rb
|
68
|
+
class Comment < ActiveRecord::Base
|
69
|
+
belongs_to :author, class_name: User
|
70
|
+
belongs_to :commentable, polymorphic: true, touch: true
|
71
|
+
end
|
72
|
+
|
73
|
+
# app/controllers/comments_controller.rb
|
74
|
+
class CommentsController < ApplicationController
|
75
|
+
before_filter :require_login
|
76
|
+
respond_to :json
|
77
|
+
def create
|
78
|
+
@comment = current_user.comments.create(comment_params)
|
79
|
+
respond_with @comment
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def comment_params
|
85
|
+
params.require(:comment).permit(:body, :commentable_type, :commentable_id)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
So to begin we know we want our Users to be the one getting the notifications, so lets go ahead and include the necessary module
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
# app/models/user.rb
|
94
|
+
class User < ActiveRecord::Base
|
95
|
+
include Notably::Notifiable
|
96
|
+
# ...
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
Now we need to create our `CommentNotification` class. I like to do that in an app/notifications directory. I'll show the finished product here, and then walk through it line by line.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# app/notifications/comment_notification.rb
|
104
|
+
class CommentNotification
|
105
|
+
include Notably::Notification
|
106
|
+
required_attributes :commentable_type, :commentable_id, :author_id
|
107
|
+
|
108
|
+
def to_html
|
109
|
+
"#{author.short_name} commented on #{link_to commentable.name, polymorphic_path(commentable)}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def receivers
|
113
|
+
commentable.comments.pluck(:author_id).uniq - [author_id]
|
114
|
+
end
|
115
|
+
|
116
|
+
def commentable
|
117
|
+
@commentable ||= commentable_type.constantize.find(commentable_id)
|
118
|
+
end
|
119
|
+
|
120
|
+
def author
|
121
|
+
@author ||= User.where(id: author_id)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
So first we include our Notification module, then define the required attributes. The next thing I did was set up the `commentable` and `author` methods for convenience sake, which uses the required attributes to look up the models they point to. Then I wrote the `to_html` method which would return something that looks like:
|
127
|
+
|
128
|
+
> Michael B. commented on [Save Our Bluths](#)
|
129
|
+
|
130
|
+
Then I define the `receivers` method which will return a list of users that have also commented on whatever it is I'm commenting on, minus the author of the comment we're currently notifying people about.
|
131
|
+
|
132
|
+
Ok, so far so good. Now we just need to hook up the notification creation. I'm sure there's some debate to be had about where the best place to put Notification creation would be, but to me it makes the most sense to have it in the controller. So...
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# app/controllers/comments_controller.rb
|
136
|
+
class CommentsController < ApplicationController
|
137
|
+
# ...
|
138
|
+
def create
|
139
|
+
@comment = current_user.comments.create(comment_params)
|
140
|
+
CommentNotification.create(@comment)
|
141
|
+
respond_with @comment
|
142
|
+
end
|
143
|
+
# ...
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
And... we're done. Let me explain a bit about how that create method is working. You can pass it an object, or a hash. The object must respond to all the required attributes. And if it's a hash, it must have a key-value for each required attribute. So I could have just as easily have done
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
CommentNotification.create(
|
151
|
+
commentable_type: @comment.commentable_type,
|
152
|
+
commentable_id: @comment.commentable_id,
|
153
|
+
author_id: current_user.id})
|
154
|
+
```
|
155
|
+
|
156
|
+
But where's the fun in that?
|
157
|
+
|
158
|
+
## Grouping
|
159
|
+
|
160
|
+
Ok so things are looking pretty good, except the Save Our Bluths post is getting kind of popular, and my notification feed looks like this:
|
161
|
+
|
162
|
+
> Michael B. commented on [Save Our Bluths](#)
|
163
|
+
|
164
|
+
> Lucille B. commented on [Save Our Bluths](#)
|
165
|
+
|
166
|
+
> Buster B. commented on [Save Our Bluths](#)
|
167
|
+
|
168
|
+
> Tobius F. commented on [Save Our Bluths](#)
|
169
|
+
|
170
|
+
It would be nicer if it looked like
|
171
|
+
|
172
|
+
> Buster B., Lucille B., Michael B., and Tobius F. commented on [Save Our Bluths](#)
|
173
|
+
|
174
|
+
And what a wonderful time to show you Notably's grouping feature! It works by defining a subset of the required attributes that you group by. So if we wanted to group our `CommentNotification` like I did above, we would write this:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# app/notifications/comment_notification.rb
|
178
|
+
class CommentNotification
|
179
|
+
include Notably::Notification
|
180
|
+
required_attributes :commentable_type, :commentable_id, :author_id
|
181
|
+
group_by :commentable_type, :commentable_id
|
182
|
+
|
183
|
+
# ...
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
So let me explain how it's doing this. When a new notification is being saved, it's going to look at the receiver's current notification list, and see if any of them match the `group_by` attributes of the one that is currently saving. If there are any, than it adds the attributes of those that are not being grouped by (in our case, just `:author_id`) to an array that is accessible to you through the `groups` method. As soon as you add the `group_by` line, you have access to all non-grouped-by attributes through the `groups` method. So lets see how that would affect our `CommentNotification` class.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
# app/notifications/comment_notification.rb
|
191
|
+
class CommentNotification
|
192
|
+
include Notably::Notification
|
193
|
+
required_attributes :commentable_type, :commentable_id, :author_id
|
194
|
+
group_by :commentable_type, :commentable_id
|
195
|
+
|
196
|
+
def to_html
|
197
|
+
"#{authors.collect(&:short_name).to_sentence} commented on #{link_to commentable.name, polymorphic_path([commentable.project, commentable])}"
|
198
|
+
end
|
199
|
+
|
200
|
+
def receivers
|
201
|
+
commentable.comments.pluck(:author_id).uniq - [author_id]
|
202
|
+
end
|
203
|
+
|
204
|
+
def commentable
|
205
|
+
@commentable ||= commentable_type.constantize.find(commentable_id)
|
206
|
+
end
|
207
|
+
|
208
|
+
def authors
|
209
|
+
@authors ||= User.where(id: groups.collect(&:author_id)).order(:first_name)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
And that's really it. `groups` returns an array of OpenStructs that have all the non-grouped-by attributes of all the notifications that are being grouped, including the current notification. But notice that (as I use in the `receivers` method) you can still access `author_id` directly, which will just give you the author_id of the current notification that's being saved.
|
215
|
+
|
216
|
+
There's one part of Grouping that I haven't mention yet, and that is `group_within`, which lets you specify the time range that the notification has to fall into in order to be eligable to be grouped. You probably don't want a notification from last week to be grouped with one that just happened. Or maybe you do, and you can set that. By default, it groups within the last_notification_read_at
|
217
|
+
time. Which I think is a good sensible default, if you're using last_notification_read_at. Otherwise it might be best to set it to a generic `4.hours.ago`. You set it by passing it a lambda or Proc. The lambda or Proc should have one argument, which will be the receiver who's notification list it's grouping from. So this might look like:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
# app/notifications/comment_notification.rb
|
221
|
+
class CommentNotification
|
222
|
+
include Notably::Notification
|
223
|
+
required_attributes :commentable_type, :commentable_id, :author_id
|
224
|
+
group_by :commentable_type, :commentable_id
|
225
|
+
group_within ->(receiver) { 4.hours.ago }
|
226
|
+
# ...
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
Or
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
group_within ->(receiver) { receiver.updated_at }
|
234
|
+
```
|
235
|
+
|
236
|
+
Or
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
group_within ->(receiver) { receiver.last_notification_read_at } # default
|
240
|
+
```
|
241
|
+
|
242
|
+
(Just a warning, even if you're setting it to something that doesn't need the receiver passed in to calculate it, you need to specify it as an argument if you're going to use lambdas. They don't like it when arguments get ignored.)
|
243
|
+
|
244
|
+
|
245
|
+
## Contributing
|
246
|
+
|
247
|
+
1. Fork it
|
248
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
249
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
250
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
251
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/notably.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "notably/configuration"
|
2
|
+
require "notably/notification"
|
3
|
+
require "notably/notifiable"
|
4
|
+
require "notably/version"
|
5
|
+
|
6
|
+
module Notably
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def config
|
10
|
+
@config ||= Configuration.new
|
11
|
+
yield @config if block_given?
|
12
|
+
@config
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Notably
|
2
|
+
module Notifiable
|
3
|
+
|
4
|
+
def notifications
|
5
|
+
parse_notifications(Notably.config.redis.zrevrangebyscore(notification_key, Time.now.to_i, 0))
|
6
|
+
end
|
7
|
+
|
8
|
+
def notifications_since(time)
|
9
|
+
parse_notifications(Notably.config.redis.zrevrangebyscore(notification_key, Time.now.to_i, time.to_i))
|
10
|
+
end
|
11
|
+
|
12
|
+
def unread_notifications
|
13
|
+
notifications_since(last_notification_read_at)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unread_notifications!
|
17
|
+
notifications_since(Notably.config.redis.getset(last_notification_read_at_key, Time.now.to_i))
|
18
|
+
end
|
19
|
+
|
20
|
+
def read_notifications
|
21
|
+
parse_notifications(Notably.config.redis.zrevrangebyscore(notification_key, last_notification_read_at, 0))
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_notifications!
|
25
|
+
parse_notifications(Notably.config.redis.set(last_notification_read_at_key, Time.now.to_i))
|
26
|
+
end
|
27
|
+
|
28
|
+
def last_notification_read_at
|
29
|
+
Notably.config.redis.get(last_notification_read_at_key).to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
def notification_key
|
33
|
+
"notably:notifications:#{self.class}:#{self.id}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_notification_read_at_key
|
37
|
+
"notably:last_read_at:#{self.class}:#{self.id}"
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def parse_notifications(notifications)
|
43
|
+
notifications.collect { |n| Marshal.load(n) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Notably
|
2
|
+
module Notification
|
3
|
+
attr_accessor :data, :created_at, :groups
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
if defined?(Rails) && defined?(ActionView)
|
8
|
+
base.send(:include, ActionView::Helpers)
|
9
|
+
base.send(:include, Rails.application.routes.url_helpers)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
ActionView::Base.full_sanitizer.sanitize(to_html) if defined?(ActionView)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_html
|
18
|
+
""
|
19
|
+
end
|
20
|
+
|
21
|
+
def receivers
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(*attributes_hashes)
|
26
|
+
@data = {}
|
27
|
+
@groups = []
|
28
|
+
attributes_hashes.each do |attributes|
|
29
|
+
case attributes
|
30
|
+
when Hash
|
31
|
+
raise ArgumentError, "Hash does not have all required attributes" unless self.class.required_attributes.all? { |k| attributes.key? k }
|
32
|
+
if @data.any?
|
33
|
+
raise ArgumentError, "Group by fields do not have shared values" unless @data == attributes.slice(*self.class.group_by)
|
34
|
+
else
|
35
|
+
@data = attributes
|
36
|
+
end
|
37
|
+
@groups << OpenStruct.new(attributes.except(*self.class.group_by))
|
38
|
+
else
|
39
|
+
raise ArgumentError, "Object #{attributes} does not respond to all required attributes" unless self.class.required_attributes.all? { |k| attributes.respond_to? k }
|
40
|
+
if @data.any?
|
41
|
+
raise ArgumentError, "Group by fields do not have shared values" unless @data == Hash[self.class.group_by.collect { |k| [k, attributes.send(k)] }]
|
42
|
+
else
|
43
|
+
@data = Hash[self.class.required_attributes.collect { |k| [k, attributes.send(k)] }]
|
44
|
+
end
|
45
|
+
@groups << OpenStruct.new(Hash[(self.class.required_attributes - self.class.group_by).collect { |k| [k, attributes.send(k)] }])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def save
|
51
|
+
receivers.each do |receiver|
|
52
|
+
# look for groupable messages within group_within
|
53
|
+
# group_within = self.class.group_within.arity == 1 ? self.class.group_within.call(user) : self.class.group_within.call
|
54
|
+
group_within = self.class.group_within.call(receiver)
|
55
|
+
groupable_notifications = receiver.notifications_since(group_within)
|
56
|
+
groupable_notifications.select! { |notification| notification[:data].slice(*self.class.group_by) == data.slice(*self.class.group_by) }
|
57
|
+
groupable_notifications.each do |notification|
|
58
|
+
@groups += notification[:groups]
|
59
|
+
end
|
60
|
+
Notably.config.redis.pipelined do
|
61
|
+
Notably.config.redis.zadd(receiver.send(:notification_key), created_at.to_i, marshal)
|
62
|
+
groupable_notifications.each do |notification|
|
63
|
+
Notably.config.redis.zrem(receiver.send(:notification_key), Marshal.dump(notification))
|
64
|
+
@groups -= notification[:groups]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_h
|
71
|
+
{
|
72
|
+
created_at: created_at,
|
73
|
+
data: data,
|
74
|
+
groups: groups,
|
75
|
+
message: to_s,
|
76
|
+
html: to_html
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def marshal
|
81
|
+
Marshal.dump(to_h)
|
82
|
+
end
|
83
|
+
|
84
|
+
def created_at
|
85
|
+
@created_at ||= Time.now
|
86
|
+
end
|
87
|
+
|
88
|
+
def method_missing(method, *args, &block)
|
89
|
+
if method.to_s =~ /=/
|
90
|
+
method = method.to_s.gsub!('=', '')
|
91
|
+
if @data.key? method
|
92
|
+
@data[method.to_sym] = *args
|
93
|
+
end
|
94
|
+
else
|
95
|
+
if @data.key? method
|
96
|
+
@data[method]
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module ClassMethods
|
104
|
+
def create(attributes={})
|
105
|
+
new(attributes).save
|
106
|
+
end
|
107
|
+
|
108
|
+
def required_attributes(*args)
|
109
|
+
if args.any?
|
110
|
+
@required_attributes ||= []
|
111
|
+
@required_attributes += args
|
112
|
+
else
|
113
|
+
@required_attributes ||= []
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def group_by(*args)
|
118
|
+
if args.any?
|
119
|
+
@group_by ||= []
|
120
|
+
@group_by += args
|
121
|
+
else
|
122
|
+
@group_by ||= []
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def group_within(block=nil)
|
127
|
+
if block
|
128
|
+
@group_within = block
|
129
|
+
else
|
130
|
+
@group_within ||= ->(receiver) { receiver.last_notification_read_at }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
data/notably.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'notably/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "notably"
|
8
|
+
spec.version = Notably::VERSION
|
9
|
+
spec.authors = ["Will Cosgrove"]
|
10
|
+
spec.email = ["will@willcosgrove.com"]
|
11
|
+
spec.description = %q{A redis backed notification system}
|
12
|
+
spec.summary = %q{A redis backed notification system}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: notably
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Will Cosgrove
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A redis backed notification system
|
42
|
+
email:
|
43
|
+
- will@willcosgrove.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/notably.rb
|
54
|
+
- lib/notably/configuration.rb
|
55
|
+
- lib/notably/notifiable.rb
|
56
|
+
- lib/notably/notification.rb
|
57
|
+
- lib/notably/version.rb
|
58
|
+
- notably.gemspec
|
59
|
+
homepage: ''
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.0.3
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: A redis backed notification system
|
83
|
+
test_files: []
|