stream_rails 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE +27 -0
- data/README.md +262 -0
- data/lib/stream_rails.rb +47 -0
- data/lib/stream_rails/activity.rb +93 -0
- data/lib/stream_rails/config.rb +27 -0
- data/lib/stream_rails/enrich.rb +104 -0
- data/lib/stream_rails/feed_manager.rb +61 -0
- data/lib/stream_rails/logger.rb +6 -0
- data/lib/stream_rails/railtie.rb +7 -0
- data/lib/stream_rails/renderable.rb +66 -0
- data/lib/stream_rails/sync_policies.rb +30 -0
- data/lib/stream_rails/utils/view_helpers.rb +15 -0
- data/lib/stream_rails/version.rb +3 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTMwN2JiNjM1YTNhYjQwZjBkNmNlMTc2YTM3ZjQ0NTllOTRjMjI3Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTY1NjJkMzc2YTJjZDI0ZjIwZTY3YjIxYTI3NWVlY2FlNTc1N2NhMg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MGQ3YWE0NjI5M2I0NzdlMDcyZjhmN2ExMjFjOWU4ZTY2MTc4NzBjYTkyZTI5
|
10
|
+
Nzg4ZTNiMTU2OWE3NzI0ZGM2ZmZmNDRjYTNlNzQ3MDA0MTU0ZDFmZDBmOThi
|
11
|
+
MzI5YjQ0M2U2NzljNjQ1MjQxNDI0ZmY2Yjg1MzZjNmI1YmZlZWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
M2Y4ODA5YWQ0OTZjZjllNjRkYjkyZTNkMmFkYzY2NGFmYmYyZjIzZmQ1ODE4
|
14
|
+
YmE1OTk1MzAyMTNlOTUwMGM1NGNlYWE2ZjE3ODIxZDAwMTljMWU0Y2VhZWRi
|
15
|
+
NjU0ZjYxZTUwMzQ4MGRhY2UxNjE2YzM4MjEzMWJkZTc5MmNkMWY=
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2014, Tommaso Barbugli
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of the {organization} nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
Stream Rails
|
2
|
+
============
|
3
|
+
|
4
|
+
[](http://travis-ci.org/GetStream/stream-rails)
|
5
|
+
|
6
|
+
|
7
|
+
This package helps you create activity streams & newsfeeds with Ruby on Rails and [GetStream.io](https://getstream.io).
|
8
|
+
|
9
|
+
###Activity Streams & Newsfeeds
|
10
|
+
|
11
|
+

|
12
|
+
|
13
|
+
What you can build:
|
14
|
+
|
15
|
+
* Activity streams such as seen on Github
|
16
|
+
* A twitter style newsfeed
|
17
|
+
* A feed like instagram/ pinterest
|
18
|
+
* Facebook style newsfeeds
|
19
|
+
* A notification system
|
20
|
+
|
21
|
+
###Table of Contents
|
22
|
+
|
23
|
+
### Gem installation
|
24
|
+
|
25
|
+
You can install ```stream_rails``` as you would any other gem:
|
26
|
+
|
27
|
+
```gem install stream_rails```
|
28
|
+
|
29
|
+
or in your Gemfile:
|
30
|
+
|
31
|
+
```gem 'stream_rails'```
|
32
|
+
|
33
|
+
|
34
|
+
### Setup
|
35
|
+
|
36
|
+
Login with Github on getstream.io and get your ```api_key``` and ```api_secret``` from your app configuration (Dashboard screen).
|
37
|
+
|
38
|
+
Then you can add the StreamRails configuration in ```config/initializers/stream_rails.rb```
|
39
|
+
|
40
|
+
```
|
41
|
+
require 'stream_rails'
|
42
|
+
|
43
|
+
StreamRails.configure do |config|
|
44
|
+
config.api_key = "YOUR API KEY"
|
45
|
+
config.api_secret = "YOUR API SECRET"
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Model configuration
|
50
|
+
|
51
|
+
Include StreamRails::Activity and add as_activity to the model you want to integrate with your feeds.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class Pin < ActiveRecord::Base
|
55
|
+
belongs_to :user
|
56
|
+
belongs_to :item
|
57
|
+
|
58
|
+
validates :item, presence: true
|
59
|
+
validates :user, presence: true
|
60
|
+
|
61
|
+
include StreamRails::Activity
|
62
|
+
as_activity
|
63
|
+
end
|
64
|
+
```
|
65
|
+
Everytime a Pin is created it will be stored in the feed of the user that created it, and when a Pin instance is deleted than it will get removed as well.
|
66
|
+
|
67
|
+
####Activity fields
|
68
|
+
|
69
|
+
Models are stored in feeds as activities. An activity is composed of at least the following data fields: **actor**, **verb**, **object**, **time**. You can also add more custom data if needed.
|
70
|
+
|
71
|
+
**object** is a reference to the model instance itself
|
72
|
+
**actor** is a reference to the user attribute of the instance
|
73
|
+
**verb** is a string representation of the class name
|
74
|
+
|
75
|
+
In order to work out-of-the-box, the Activity class makes makes few assumptions:
|
76
|
+
|
77
|
+
1. the Model class belongs to a user
|
78
|
+
2. the model table has timestamp columns (created_at is required)
|
79
|
+
|
80
|
+
You can change this behaviour by overriding ```#activity_actor```.
|
81
|
+
|
82
|
+
Below shows an example how to change your class if the model belongs to an author instead of to a user.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class Pin < ActiveRecord::Base
|
86
|
+
belongs_to :author
|
87
|
+
belongs_to :item
|
88
|
+
|
89
|
+
include StreamRails::Activity
|
90
|
+
as_activity
|
91
|
+
|
92
|
+
def activity_actor
|
93
|
+
self.author
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
####Activity extra data
|
100
|
+
|
101
|
+
Often you'll want to store more data than just the basic fields. You achieve this by implementing ```#activity_extra_data``` in your model.
|
102
|
+
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Pin < ActiveRecord::Base
|
106
|
+
belongs_to :author
|
107
|
+
belongs_to :item
|
108
|
+
|
109
|
+
include StreamRails::Activity
|
110
|
+
as_activity
|
111
|
+
|
112
|
+
def activity_extra_data
|
113
|
+
{'is_retweet' => self.is_retweet}
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
###Feed manager
|
120
|
+
|
121
|
+
```stream_rails``` comes with a Feed Manager class that helps with all common feed operations. You can get an instance of the manager with ```StreamRails.feed_manager```.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
feed = StreamRails.feed_manager.get_user_feed(current_user.id)
|
125
|
+
```
|
126
|
+
|
127
|
+
####Feeds bundled with feed_manager
|
128
|
+
|
129
|
+
To get you started the manager has 4 feeds pre configured. You can add more feeds if your application needs it.
|
130
|
+
Feeds are divided in three categories.
|
131
|
+
|
132
|
+
#####User feed:
|
133
|
+
The user feed stores all activities for a user. Think of it as your personal Facebook page. You can easily get this feed from the manager.
|
134
|
+
```ruby
|
135
|
+
feed = StreamRails.feed_manager.get_user_feed(current_user.id)
|
136
|
+
```
|
137
|
+
|
138
|
+
#####News feeds:
|
139
|
+
The news feeds store the activities from the people you follow.
|
140
|
+
There is both a flat newsfeed (similar to twitter) and an aggregated newsfeed (like facebook).
|
141
|
+
|
142
|
+
```php
|
143
|
+
feed = StreamRails.feed_manager.get_news_feeds(current_user.id)[:flat]
|
144
|
+
aggregated_feed = StreamRails.feed_manager.get_news_feeds(current_user.id)[:aggregated]
|
145
|
+
```
|
146
|
+
|
147
|
+
#####Notification feed:
|
148
|
+
The notification feed can be used to build notification functionality.
|
149
|
+
|
150
|
+

|
151
|
+
|
152
|
+
Below we show an example of how you can read the notification feed.
|
153
|
+
```ruby
|
154
|
+
notification_feed = StreamRails.feed_manager.get_notification_feed(current_user.id)
|
155
|
+
|
156
|
+
```
|
157
|
+
By default the notification feed will be empty. You can specify which users to notify when your model gets created. In the case of a retweet you probably want to notify the user of the parent tweet.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class Pin < ActiveRecord::Base
|
161
|
+
belongs_to :author
|
162
|
+
belongs_to :item
|
163
|
+
|
164
|
+
include StreamRails::Activity
|
165
|
+
as_activity
|
166
|
+
|
167
|
+
def activity_notify
|
168
|
+
if self.is_retweet
|
169
|
+
[feed_manager.get_notification_feed(self.parent.user_id)]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Another example would be following a user. You would commonly want to notify the user which is being followed.
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
class Follow < ActiveRecord::Base
|
180
|
+
belongs_to :user
|
181
|
+
belongs_to :target
|
182
|
+
|
183
|
+
validates :target_id, presence: true
|
184
|
+
validates :user, presence: true
|
185
|
+
|
186
|
+
include StreamRails::Activity
|
187
|
+
as_activity
|
188
|
+
|
189
|
+
def activity_notify
|
190
|
+
[feed_manager.get_notification_feed(self.target_id)]
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
####Follow a feed
|
197
|
+
The create the newsfeeds you need to notify the system about follow relationships. The manager comes with APIs to let a user's news feeds follow another user's feed. This code lets the current user's flat and aggregated feeds follow the target_user's personal feed.
|
198
|
+
|
199
|
+
```
|
200
|
+
StreamRails.feed_manager.follow_user(user_id, target_id)
|
201
|
+
```
|
202
|
+
|
203
|
+
### Showing the newsfeed
|
204
|
+
|
205
|
+
####Activity enrichment
|
206
|
+
|
207
|
+
When you read data from feeds, a pin activity will look like this:
|
208
|
+
|
209
|
+
```json
|
210
|
+
{"actor": "User:1", "verb": "like", "object": "Pin:42"}
|
211
|
+
```
|
212
|
+
|
213
|
+
This is far from ready for usage in your template. We call the process of loading the references from the database enrichment. An example is shown below:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
enricher = StreamRails::Enrich.new
|
217
|
+
|
218
|
+
feed = StreamRails.feed_manager.get_news_feeds(current_user.id)[:flat]
|
219
|
+
results = feed.get()['results']
|
220
|
+
activities = enricher.enrich_activities(results)
|
221
|
+
```
|
222
|
+
|
223
|
+
####Templating
|
224
|
+
|
225
|
+
Now that you've enriched the activities you can render them in a view.
|
226
|
+
For convenience we includes a basic view:
|
227
|
+
|
228
|
+
```
|
229
|
+
<div class="container">
|
230
|
+
<div class="container-pins">
|
231
|
+
<% for activity in @activities %>
|
232
|
+
<%= render_activity activity %>
|
233
|
+
<% end %>
|
234
|
+
</div>
|
235
|
+
</div>
|
236
|
+
```
|
237
|
+
|
238
|
+
The ```render_activity``` view helper will render the activity by picking the partial ```activity/_pin``` for a pin activity, ```aggregated/_follow``` for an aggregated activity with verb follow.
|
239
|
+
|
240
|
+
The helper will automatically send ```activity``` to the local scope of the partial; additional parameters can be send as well as use different layouts, and prefix the name
|
241
|
+
|
242
|
+
|
243
|
+
eg. renders the activity partial using the ```small_activity``` layout.
|
244
|
+
|
245
|
+
```
|
246
|
+
<%= render_activity activity, :layout => "small_activity" %>
|
247
|
+
```
|
248
|
+
|
249
|
+
|
250
|
+
eg. prefixes the name of the template with "notification_"
|
251
|
+
|
252
|
+
```
|
253
|
+
<%= render_activity activity, :prefix => "notification_" %>
|
254
|
+
```
|
255
|
+
|
256
|
+
eg. adds the extra_var to the partial scope
|
257
|
+
|
258
|
+
```
|
259
|
+
<%= render_activity activity, :locals => {:extra_var => 42} %>
|
260
|
+
```
|
261
|
+
|
262
|
+
|
data/lib/stream_rails.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'action_view'
|
3
|
+
require 'stream'
|
4
|
+
require 'stream_rails/enrich'
|
5
|
+
require 'stream_rails/logger'
|
6
|
+
|
7
|
+
module StreamRails
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :Activity
|
11
|
+
autoload :Config
|
12
|
+
autoload :FeedManager, 'stream_rails/feed_manager'
|
13
|
+
autoload :Renderable
|
14
|
+
autoload :VERSION
|
15
|
+
|
16
|
+
def self.client
|
17
|
+
Stream::Client.new(self.config.api_key, self.config.api_secret)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns StreamRails's configuration object.
|
21
|
+
def self.config
|
22
|
+
@config ||= StreamRails::Config.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns StreamRails's configuration object.
|
26
|
+
def self.feed_manager
|
27
|
+
@feed_manager ||= StreamRails::FeedManager.new(self.client, self.config.feed_configs)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Lets you set global configuration options.
|
31
|
+
#
|
32
|
+
# All available options and their defaults are in the example below:
|
33
|
+
# @example Initializer for Rails
|
34
|
+
# StreamRails.configure do |config|
|
35
|
+
# config.api_key = "key"
|
36
|
+
# config.api_secret = "secret"
|
37
|
+
# config.api_site_id = "42"
|
38
|
+
# config.enabled = true
|
39
|
+
# end
|
40
|
+
def self.configure(&block)
|
41
|
+
yield(config) if block_given?
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'stream_rails/utils/view_helpers'
|
47
|
+
require 'stream_rails/railtie' if defined?(Rails)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'stream_rails/sync_policies'
|
3
|
+
|
4
|
+
module StreamRails
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def create_reference(record)
|
8
|
+
if record.is_a? ActiveRecord::Base
|
9
|
+
"#{record.class.model_name}:#{record.id}"
|
10
|
+
else
|
11
|
+
record.to_s unless record.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def as_activity(opts = {})
|
19
|
+
default_opts = {:track_deletes => true, :sync_policy => nil}
|
20
|
+
options = default_opts.merge(opts)
|
21
|
+
if options[:sync_policy].nil?
|
22
|
+
include StreamRails::SyncPolicy::SyncCreate
|
23
|
+
if options[:track_deletes]
|
24
|
+
include StreamRails::SyncPolicy::SyncDestroy
|
25
|
+
end
|
26
|
+
else
|
27
|
+
include options[:sync_policy]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
module Activity
|
34
|
+
|
35
|
+
def self.included base
|
36
|
+
base.extend ClassMethods
|
37
|
+
end
|
38
|
+
|
39
|
+
def activity_owner_id
|
40
|
+
self.user_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def activity_actor
|
44
|
+
self.user
|
45
|
+
end
|
46
|
+
|
47
|
+
def activity_owner_feed
|
48
|
+
'user'
|
49
|
+
end
|
50
|
+
|
51
|
+
def activity_actor_id
|
52
|
+
StreamRails.create_reference(self.activity_actor)
|
53
|
+
end
|
54
|
+
|
55
|
+
def activity_verb
|
56
|
+
self.class.model_name.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def activity_object_id
|
60
|
+
StreamRails.create_reference(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def activity_foreign_id
|
64
|
+
StreamRails.create_reference(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def activity_notify
|
68
|
+
end
|
69
|
+
|
70
|
+
def activity_extra_data
|
71
|
+
{}
|
72
|
+
end
|
73
|
+
|
74
|
+
def activity_time
|
75
|
+
self.created_at
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_activity
|
79
|
+
activity = {
|
80
|
+
:actor => self.activity_actor_id,
|
81
|
+
:verb => self.activity_verb,
|
82
|
+
:object => self.activity_object_id,
|
83
|
+
:foreign_id => self.activity_foreign_id,
|
84
|
+
:time => self.activity_time,
|
85
|
+
}
|
86
|
+
if !self.activity_notify.nil?
|
87
|
+
activity[:to] = self.activity_notify.map{|f| f.feed_id}
|
88
|
+
end
|
89
|
+
activity.merge(self.activity_extra_data)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module StreamRails
|
2
|
+
# Class used to initialize configuration object.
|
3
|
+
class Config
|
4
|
+
attr_accessor :api_key
|
5
|
+
attr_accessor :api_secret
|
6
|
+
attr_accessor :api_site_id
|
7
|
+
attr_accessor :enabled
|
8
|
+
|
9
|
+
attr_accessor :news_feeds
|
10
|
+
attr_accessor :notification_feed
|
11
|
+
attr_accessor :user_feed
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@enabled = true
|
15
|
+
@news_feeds = {:flat=>'flat', :aggregated=>'aggregated'}
|
16
|
+
@notification_feed = 'notification'
|
17
|
+
@user_feed = 'user'
|
18
|
+
end
|
19
|
+
|
20
|
+
def feed_configs
|
21
|
+
{:news_feeds=>@news_feeds,
|
22
|
+
:notification_feed=>@notification_feed,
|
23
|
+
:user_feed=>@user_feed}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module StreamRails
|
4
|
+
|
5
|
+
class ActivityResult < Hash
|
6
|
+
attr_accessor :enriched
|
7
|
+
attr_reader :failed_to_enrich
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@failed_to_enrich = Hash.new
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_activity(h)
|
15
|
+
self.merge(h)
|
16
|
+
end
|
17
|
+
|
18
|
+
def enriched?
|
19
|
+
@failed_to_enrich.keys.length == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def not_enriched_fields
|
23
|
+
@failed_to_enrich.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def track_not_enriched_field(field, value = nil)
|
27
|
+
@failed_to_enrich[field] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class Enrich
|
33
|
+
|
34
|
+
def initialize(fields = nil)
|
35
|
+
@fields = fields || [:actor, :object]
|
36
|
+
end
|
37
|
+
|
38
|
+
def model_field?(field_value)
|
39
|
+
bits = field_value.split(':')
|
40
|
+
if bits.length < 2
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
begin
|
44
|
+
bits[0].classify.constantize
|
45
|
+
rescue NameError
|
46
|
+
return false
|
47
|
+
else
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def enrich_activities(activities)
|
53
|
+
references = self.collect_references(activities)
|
54
|
+
objects = self.retrieve_objects(references)
|
55
|
+
self.inject_objects(activities, objects)
|
56
|
+
end
|
57
|
+
|
58
|
+
def enrich_aggregated_activities(aggregated_activities)
|
59
|
+
references = Hash.new
|
60
|
+
aggregated_activities.each do |aggregated|
|
61
|
+
refs = self.collect_references(aggregated['activities'])
|
62
|
+
references = references.merge(refs){|key, v1, v2| v1.merge(v2)}
|
63
|
+
end
|
64
|
+
objects = self.retrieve_objects(references)
|
65
|
+
aggregated_activities.each do |aggregated|
|
66
|
+
aggregated['activities'] = self.inject_objects(aggregated['activities'], objects)
|
67
|
+
end
|
68
|
+
aggregated_activities.map {|a| ActivityResult.new().from_activity(a)}
|
69
|
+
end
|
70
|
+
|
71
|
+
def collect_references(activities)
|
72
|
+
model_refs = Hash.new{ |h,k| h[k] = Hash.new}
|
73
|
+
activities.each do |activity|
|
74
|
+
activity.select{|k,v| @fields.include? k.to_sym}.each do |field, value|
|
75
|
+
next unless self.model_field?(value)
|
76
|
+
model, id = value.split(':')
|
77
|
+
model_refs[model][id] = 0
|
78
|
+
end
|
79
|
+
end
|
80
|
+
model_refs
|
81
|
+
end
|
82
|
+
|
83
|
+
def retrieve_objects(references)
|
84
|
+
Hash[references.map{ |model, ids| [model, Hash[model.classify.constantize.where(id: ids.keys).map {|i| [i.id.to_s, i]}] ] }]
|
85
|
+
end
|
86
|
+
|
87
|
+
def inject_objects(activities, objects)
|
88
|
+
activities = activities.map {|a| ActivityResult.new().from_activity(a)}
|
89
|
+
activities.each do |activity|
|
90
|
+
activity.select{|k,v| @fields.include? k.to_sym}.each do |field, value|
|
91
|
+
next unless self.model_field?(value)
|
92
|
+
model, id = value.split(':')
|
93
|
+
activity[field] = objects[model][id] || value
|
94
|
+
if objects[model][id].nil?
|
95
|
+
activity.track_not_enriched_field(field, value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
activities
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module StreamRails
|
2
|
+
# Class used to manage feeds
|
3
|
+
class FeedManager
|
4
|
+
attr_reader :client
|
5
|
+
|
6
|
+
def initialize(client, opts={})
|
7
|
+
@client = client
|
8
|
+
@user_feed = opts[:user_feed]
|
9
|
+
@news_feeds = opts[:news_feeds]
|
10
|
+
@notification_feed = opts[:notification_feed]
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_user_feed(user_id)
|
14
|
+
@client.feed("#{@user_feed}:#{user_id}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_news_feeds(user_id)
|
18
|
+
Hash[@news_feeds.map{ |k,v| [k, self.get_feed(k, user_id)] }]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_notification_feed(user_id)
|
22
|
+
@client.feed("#{@notification_feed}:#{user_id}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_feed(feed_type, user_id)
|
26
|
+
@client.feed("#{feed_type}:#{user_id}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def follow_user(user_id, target_id)
|
30
|
+
target_feed = self.get_user_feed(target_id)
|
31
|
+
@news_feeds.each do |_, feed|
|
32
|
+
news_feed = self.get_feed(feed, user_id)
|
33
|
+
news_feed.follow(target_feed.feed_id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def unfollow_user(user_id, target_id)
|
38
|
+
target_feed = self.get_user_feed(target_id)
|
39
|
+
@news_feeds.each do |_, feed|
|
40
|
+
news_feed = self.get_feed(feed, user_id)
|
41
|
+
news_feed.unfollow(target_feed.feed_id)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_owner_feed(instance)
|
46
|
+
self.get_feed(instance.activity_owner_feed, instance.activity_owner_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def created_activity(instance)
|
50
|
+
activity = instance.create_activity
|
51
|
+
feed = self.get_owner_feed(instance)
|
52
|
+
feed.add_activity(activity)
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroyed_activity(instance)
|
56
|
+
feed = self.get_owner_feed(instance)
|
57
|
+
feed.remove(instance.activity_foreign_id, foreign_id=true)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module StreamRails
|
2
|
+
# Provides logic for rendering activities. (different templates per activity verb).
|
3
|
+
module Renderable
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def render(activity, context, params = {})
|
8
|
+
aggregated = activity.has_key? 'activities'
|
9
|
+
partial = partial_path(activity, aggregated, *params.values_at(:prefix, :partial, :partial_root))
|
10
|
+
layout = layout_path(*params.values_at(:layout, :layout_root))
|
11
|
+
locals = prepare_locals(activity, params)
|
12
|
+
params = params.merge(partial: partial, locals: locals, layout: layout)
|
13
|
+
if aggregated
|
14
|
+
render_aggregated(activity, context, params)
|
15
|
+
else
|
16
|
+
render_simple(activity, context, params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_simple(activity, context, params)
|
21
|
+
if activity.enriched?
|
22
|
+
context.render params
|
23
|
+
else
|
24
|
+
StreamRails.logger.warn "trying to display a non enriched activity #{activity.inspect} #{activity.failed_to_enrich}"
|
25
|
+
return ''
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_aggregated(activity, context, params)
|
30
|
+
if !activity['activities'].map {|a| !a.enriched?}.all?
|
31
|
+
context.render params
|
32
|
+
else
|
33
|
+
first_activity = activity['activities'][0]
|
34
|
+
StreamRails.logger.warn "trying to display a non enriched activity #{first_activity.inspect} #{first_activity.failed_to_enrich}"
|
35
|
+
return ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def layout_path(path = nil, root = nil)
|
40
|
+
path.nil? and return
|
41
|
+
root ||= 'layouts'
|
42
|
+
select_path path, root
|
43
|
+
end
|
44
|
+
|
45
|
+
def partial_path(activity, aggregated, prefix = '', path = nil, root = nil)
|
46
|
+
root ||= (if aggregated then 'aggregated_activity' else 'activity' end)
|
47
|
+
path ||= "#{activity['verb']}".downcase
|
48
|
+
path = "#{prefix}_#{path}" if prefix
|
49
|
+
select_path path, root
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare_locals(activity, params)
|
53
|
+
locals = params.delete(:locals) || Hash.new
|
54
|
+
locals.merge\
|
55
|
+
activity: activity,
|
56
|
+
parameters: params
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def select_path path, root
|
61
|
+
[root, path].map(&:to_s).join('/')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module StreamRails
|
2
|
+
|
3
|
+
module SyncPolicy
|
4
|
+
|
5
|
+
module SyncCreate
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.after_commit :add_to_feed, on: [:create]
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def add_to_feed
|
13
|
+
StreamRails.feed_manager.created_activity(self)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module SyncDestroy
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.after_commit :remove_from_feed, on: [:destroy]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def remove_from_feed
|
25
|
+
StreamRails.feed_manager.destroyed_activity(self)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Provides a shortcut from views to the rendering method.
|
2
|
+
module StreamRails
|
3
|
+
# Module extending ActionView::Base and adding `render_activity` helper.
|
4
|
+
module ViewHelpers
|
5
|
+
# View helper for rendering an activity
|
6
|
+
def render_activity activity, options = {}
|
7
|
+
Renderable.render(activity, self, options)
|
8
|
+
end
|
9
|
+
# View helper for rendering many activities
|
10
|
+
def render_activities activities, options = {}
|
11
|
+
activities.map {|activity| Renderable.render(activity, self, options.dup) }.join.html_safe
|
12
|
+
end
|
13
|
+
end
|
14
|
+
ActionView::Base.class_eval { include ViewHelpers }
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stream_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tommaso Barbugli
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: actionpack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: railties
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: stream-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fakeweb
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.10'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2.10'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.7.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.7.1
|
139
|
+
description:
|
140
|
+
email: tbarbugli@gmail.com
|
141
|
+
executables: []
|
142
|
+
extensions: []
|
143
|
+
extra_rdoc_files:
|
144
|
+
- README.md
|
145
|
+
- LICENSE
|
146
|
+
files:
|
147
|
+
- LICENSE
|
148
|
+
- README.md
|
149
|
+
- lib/stream_rails.rb
|
150
|
+
- lib/stream_rails/activity.rb
|
151
|
+
- lib/stream_rails/config.rb
|
152
|
+
- lib/stream_rails/enrich.rb
|
153
|
+
- lib/stream_rails/feed_manager.rb
|
154
|
+
- lib/stream_rails/logger.rb
|
155
|
+
- lib/stream_rails/railtie.rb
|
156
|
+
- lib/stream_rails/renderable.rb
|
157
|
+
- lib/stream_rails/sync_policies.rb
|
158
|
+
- lib/stream_rails/utils/view_helpers.rb
|
159
|
+
- lib/stream_rails/version.rb
|
160
|
+
homepage: http://github.com/tbarbugli/stream-ruby
|
161
|
+
licenses:
|
162
|
+
- Apache-2.0
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ! '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 1.9.2
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ! '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 2.2.2
|
181
|
+
signing_key:
|
182
|
+
specification_version: 4
|
183
|
+
summary: A gem that provides a client interface for getstream.io
|
184
|
+
test_files: []
|
185
|
+
has_rdoc: true
|