activity_feed 1.4.0 → 2.0.0.rc1

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.
@@ -1,29 +1,98 @@
1
- require 'leaderboard'
2
-
3
1
  module ActivityFeed
4
- class Feed
5
- def initialize(user_id)
6
- @feederboard = Leaderboard.new(ActivityFeed.feed_key(user_id), Leaderboard::DEFAULT_OPTIONS, {:redis_connection => ActivityFeed.redis})
7
- @feederboard_aggregate = Leaderboard.new(ActivityFeed.feed_key(user_id, true), Leaderboard::DEFAULT_OPTIONS, {:redis_connection => ActivityFeed.redis})
2
+ module Feed
3
+ # Retrieve a page from the activity feed for a given +user_id+. You can configure
4
+ # +ActivityFeed.item_loader+ with a Proc to retrieve an item from, for example,
5
+ # your ORM (e.g. ActiveRecord) or your ODM (e.g. Mongoid), and have the page
6
+ # returned with loaded items rather than item IDs.
7
+ #
8
+ # @param user_id [String] User ID.
9
+ # @param page [int] Page in the feed to be retrieved.
10
+ # @param aggregate [boolean, false] Whether to retrieve the aggregate feed for +user_id+.
11
+ #
12
+ # @return page from the activity feed for a given +user_id+.
13
+ def feed(user_id, page, aggregate = ActivityFeed.aggregate)
14
+ feederboard = ActivityFeed.feederboard_for(user_id, aggregate)
15
+ feed = feederboard.members(page, :page_size => ActivityFeed.page_size).inject([]) do |feed_items, feed_item|
16
+ item = if ActivityFeed.item_loader
17
+ ActivityFeed.item_loader.call(feed_item[:member])
18
+ else
19
+ feed_item[:member]
20
+ end
21
+
22
+ feed_items << item unless item.nil?
23
+ feed_items
24
+ end
25
+
26
+ feed.nil? ? [] : feed
8
27
  end
9
-
10
- def page(page, aggregate = false)
11
- feed_items = []
12
-
13
- feed = aggregate ? @feederboard_aggregate : @feederboard
14
- feed.leaders(page).each do |feed_item|
15
- feed_items << ActivityFeed.load_item(feed_item[:member])
28
+
29
+ # Retrieve a page from the activity feed for a given +user_id+ between a
30
+ # +starting_timestamp+ and an +ending_timestamp+. You can configure
31
+ # +ActivityFeed.item_loader+ with a Proc to retrieve an item from, for example,
32
+ # your ORM (e.g. ActiveRecord) or your ODM (e.g. Mongoid), and have the feed data
33
+ # returned with loaded items rather than item IDs.
34
+ #
35
+ # @param user_id [String] User ID.
36
+ # @param starting_timestamp [int] Starting timestamp between which items in the feed are to be retrieved.
37
+ # @param ending_timestamp [int] Ending timestamp between which items in the feed are to be retrieved.
38
+ # @param aggregate [boolean, false] Whether to retrieve items from the aggregate feed for +user_id+.
39
+ #
40
+ # @return feed items from the activity feed for a given +user_id+ between the +starting_timestamp+ and +ending_timestamp+.
41
+ def feed_between_timestamps(user_id, starting_timestamp, ending_timestamp, aggregate = ActivityFeed.aggregate)
42
+ feederboard = ActivityFeed.feederboard_for(user_id, aggregate)
43
+ feed = feederboard.members_from_score_range(starting_timestamp, ending_timestamp).inject([]) do |feed_items, feed_item|
44
+ item = if ActivityFeed.item_loader
45
+ ActivityFeed.item_loader.call(feed_item[:member])
46
+ else
47
+ feed_item[:member]
48
+ end
49
+
50
+ feed_items << item unless item.nil?
51
+ feed_items
16
52
  end
17
53
 
18
- feed_items
54
+ feed.nil? ? [] : feed
19
55
  end
20
-
21
- def total_pages(aggregate = false)
22
- aggregate ? @feederboard_aggregate.total_pages : @feederboard.total_pages
56
+
57
+ # Return the total number of pages in the activity feed.
58
+ #
59
+ # @param user_id [String] User ID.
60
+ # @param aggregate [boolean, false] Whether to check the total number of pages in the aggregate activity feed or not.
61
+ # @param page_size [int, ActivityFeed.page_size] Page size to be used in calculating the total number of pages in the activity feed.
62
+ #
63
+ # @return the total number of pages in the activity feed.
64
+ def total_pages_in_feed(user_id, aggregate = ActivityFeed.aggregate, page_size = ActivityFeed.page_size)
65
+ ActivityFeed.feederboard_for(user_id, aggregate).total_pages_in(ActivityFeed.feed_key(user_id, aggregate), page_size)
23
66
  end
24
-
25
- def total_items(aggregate = false)
26
- aggregate ? @feederboard_aggregate.total_members : @feederboard.total_members
67
+
68
+ # Return the total number of items in the activity feed.
69
+ #
70
+ # @param user_id [String] User ID.
71
+ # @param aggregate [boolean, false] Whether to check the total number of items in the aggregate activity feed or not.
72
+ #
73
+ # @return the total number of items in the activity feed.
74
+ def total_items_in_feed(user_id, aggregate = ActivityFeed.aggregate)
75
+ ActivityFeed.feederboard_for(user_id, aggregate).total_members
76
+ end
77
+
78
+ # Remove the activity feeds for a given +user_id+.
79
+ #
80
+ # @param user_id [String] User ID.
81
+ def remove_feeds(user_id)
82
+ ActivityFeed.redis.multi do |transaction|
83
+ transaction.del(ActivityFeed.feed_key(user_id, false))
84
+ transaction.del(ActivityFeed.feed_key(user_id, true))
85
+ end
86
+ end
87
+
88
+ # Trim an activity feed between two timestamps.
89
+ #
90
+ # @param user_id [String] User ID.
91
+ # @param starting_timestamp [int] Starting timestamp after which activity feed items will be cut.
92
+ # @param ending_timestamp [int] Ending timestamp before which activity feed items will be cut.
93
+ # @param aggregate [boolean, false] Whether or not to trim the aggregate activity feed or not.
94
+ def trim_feed(user_id, starting_timestamp, ending_timestamp, aggregate = ActivityFeed.aggregate)
95
+ ActivityFeed.feederboard_for(user_id, aggregate).remove_members_in_score_range(starting_timestamp, ending_timestamp)
27
96
  end
28
97
  end
29
98
  end
@@ -0,0 +1,44 @@
1
+ module ActivityFeed
2
+ module Item
3
+ # Add or update an item in the activity feed for a given +user_id+.
4
+ #
5
+ # @param user_id [String] User ID.
6
+ # @param item_id [String] Item ID.
7
+ # @param timestamp [int] Timestamp for the item being added or updated.
8
+ # @param aggregate [boolean, false] Whether to add or update the item in the aggregate feed for +user_id+.
9
+ def update_item(user_id, item_id, timestamp, aggregate = ActivityFeed.aggregate)
10
+ feederboard = ActivityFeed.feederboard_for(user_id, false)
11
+ feederboard.rank_member(item_id, timestamp)
12
+
13
+ if aggregate
14
+ feederboard = ActivityFeed.feederboard_for(user_id, true)
15
+ feederboard.rank_member(item_id, timestamp)
16
+ end
17
+ end
18
+
19
+ # Specifically aggregate an item in the activity feed for a given +user_id+.
20
+ # This is useful if you are going to background the process of populating
21
+ # a user's activity feed from friend's activities.
22
+ #
23
+ # @param user_id [String] User ID.
24
+ # @param item_id [String] Item ID.
25
+ # @param timestamp [int] Timestamp for the item being added or updated.
26
+ def aggregate_item(user_id, item_id, timestamp)
27
+ feederboard = ActivityFeed.feederboard_for(user_id, true)
28
+ feederboard.rank_member(item_id, timestamp)
29
+ end
30
+
31
+ # Remove an item from the activity feed for a given +user_id+. This
32
+ # will also remove the item from the aggregate activity feed for the
33
+ # user.
34
+ #
35
+ # @param user_id [String] User ID.
36
+ # @param item_id [String] Item ID.
37
+ def remove_item(user_id, item_id)
38
+ feederboard = ActivityFeed.feederboard_for(user_id, false)
39
+ feederboard.remove_member(item_id)
40
+ feederboard = ActivityFeed.feederboard_for(user_id, true)
41
+ feederboard.remove_member(item_id)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module ActivityFeed
2
+ module Utility
3
+ # Feed key for a +user_id+ composed of:
4
+ #
5
+ # Feed: +ActivityFeed.namespace:user_id+
6
+ # Aggregate feed: +ActivityFeed.namespace:ActivityFeed.aggregate_key:user_id+
7
+ #
8
+ # @return feed key.
9
+ def feed_key(user_id, aggregate = ActivityFeed.aggregate)
10
+ aggregate ?
11
+ "#{ActivityFeed.namespace}:#{ActivityFeed.aggregate_key}:#{user_id}" :
12
+ "#{ActivityFeed.namespace}:#{user_id}"
13
+ end
14
+
15
+ # Retrieve a reference to the activity feed for a given +user_id+.
16
+ #
17
+ # @param user_id [String] User ID.
18
+ # @param aggregate [boolean, false] Whether to retrieve the aggregate feed for +user_id+ or not.
19
+ #
20
+ # @return reference to the activity feed for a given +user_id+.
21
+ def feederboard_for(user_id, aggregate = ActivityFeed.aggregate)
22
+ ::Leaderboard.new(feed_key(user_id, aggregate), ::Leaderboard::DEFAULT_OPTIONS, {:redis_connection => ActivityFeed.redis})
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module ActivityFeed
2
- VERSION = "1.4.0"
2
+ VERSION = "2.0.0.rc1"
3
3
  end
data/lib/activity_feed.rb CHANGED
@@ -1,113 +1,14 @@
1
- require 'activity_feed/version'
1
+ require 'activity_feed/configuration'
2
+ require 'activity_feed/item'
2
3
  require 'activity_feed/feed'
3
- require 'active_support/core_ext/module/attribute_accessors'
4
- require 'active_support/inflector'
4
+ require 'activity_feed/utility'
5
+ require 'activity_feed/version'
5
6
 
6
- require 'redis'
7
+ require 'leaderboard'
7
8
 
8
9
  module ActivityFeed
9
- mattr_accessor :redis
10
- mattr_accessor :namespace
11
- mattr_accessor :key
12
- mattr_accessor :persistence
13
- mattr_accessor :aggregate_key
14
- mattr_accessor :aggregate
15
-
16
- def self.persistence=(type = :memory)
17
- @@persistence_type = type
18
-
19
- case type
20
- when :active_record
21
- require 'activity_feed/active_record/item'
22
- klazz = ActivityFeed::ActiveRecord::Item
23
- when :memory
24
- require 'activity_feed/memory/item'
25
- klazz = ActivityFeed::Memory::Item
26
- when :mongo_mapper
27
- require 'activity_feed/mongo_mapper/item'
28
- klazz = ActivityFeed::MongoMapper::Item
29
- when :mongoid
30
- require 'activity_feed/mongoid/item'
31
- klazz = ActivityFeed::Mongoid::Item
32
- when :ohm
33
- require 'activity_feed/ohm/item'
34
- klazz = ActivityFeed::Ohm::Item
35
- else
36
- klazz = "ActivityFeed::#{type.to_s.classify}::Item".constantize
37
- end
38
-
39
- @@persistence = klazz
40
- end
41
-
42
- def self.create_item(attributes, aggregate = ActivityFeed.aggregate)
43
- item = @@persistence.new(attributes)
44
- item.save
45
- if aggregate
46
- ([item.user_id] | Array(aggregate)).each do |aggregation_id|
47
- ActivityFeed.aggregate_item(item, aggregation_id)
48
- end
49
- end
50
- item
51
- end
52
-
53
- def self.aggregate_item(item, user_id = nil)
54
- user_id_for_aggregate = user_id.nil? ? item.user_id : user_id
55
- case @@persistence_type
56
- when :active_record, :mongo_mapper, :mongoid
57
- ActivityFeed.redis.zadd(ActivityFeed.feed_key(user_id_for_aggregate, true), item.created_at.to_i, item.id)
58
- when :ohm
59
- ActivityFeed.redis.zadd(ActivityFeed.feed_key(user_id_for_aggregate, true), DateTime.parse(item.created_at).to_i, item.id)
60
- else
61
- ActivityFeed.redis.zadd(ActivityFeed.feed_key(user_id_for_aggregate, true), DateTime.now.to_i, item.attributes.to_json)
62
- end
63
- end
64
-
65
- def self.load_item(item_or_item_id)
66
- case @@persistence_type
67
- when :active_record
68
- ActivityFeed::ActiveRecord::Item.find(item_or_item_id)
69
- when :memory
70
- JSON.parse(item_or_item_id)
71
- when :mongo_mapper
72
- ActivityFeed::MongoMapper::Item.find(item_or_item_id)
73
- when :mongoid
74
- ActivityFeed::Mongoid::Item.find(item_or_item_id)
75
- when :ohm
76
- ActivityFeed::Ohm::Item[item_or_item_id]
77
- else
78
- @@persistence.find(item_or_item_id)
79
- end
80
- end
81
-
82
- def self.update_item(user_id, item_id, timestamp, aggregate = false)
83
- unless @@persistence_type == :memory
84
- key = feed_key(user_id, aggregate)
85
- ActivityFeed.redis.zadd(key, timestamp, item_id)
86
- end
87
- end
88
-
89
- def self.delete_item(user_id, item_id, aggregate = false)
90
- unless @@persistence_type == :memory
91
- key = feed_key(user_id, aggregate)
92
- ActivityFeed.redis.zrem(key, item_id)
93
- end
94
- end
95
-
96
- def self.feed_key(user_id, aggregate = false)
97
- if aggregate
98
- "#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{ActivityFeed.aggregate_key}:#{user_id}"
99
- else
100
- "#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}"
101
- end
102
- end
103
-
104
- def self.feed(user_id)
105
- ActivityFeed::Feed.new(user_id)
106
- end
107
-
108
- self.namespace = 'activity'
109
- self.key = 'feed'
110
- self.aggregate_key = 'aggregate'
111
- self.aggregate = []
112
- self.persistence = :memory
113
- end
10
+ extend Configuration
11
+ extend Item
12
+ extend Feed
13
+ extend Utility
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActivityFeed::Configuration do
4
+ describe '#configure' do
5
+ it 'should have default attributes' do
6
+ ActivityFeed.configure do |configuration|
7
+ configuration.namespace.should == 'activity_feed'
8
+ configuration.aggregate.should be_false
9
+ configuration.aggregate_key.should == 'aggregate'
10
+ configuration.page_size.should== 25
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,270 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActivityFeed::Feed do
4
+ describe '#feed' do
5
+ describe 'without aggregation' do
6
+ it 'should return an activity feed with the items correctly ordered' do
7
+ add_items_to_feed('david')
8
+
9
+ feed = ActivityFeed.feed('david', 1)
10
+ feed.length.should == 5
11
+ feed[0].to_i.should == 5
12
+ feed[4].to_i.should == 1
13
+ end
14
+ end
15
+
16
+ describe 'with aggregation' do
17
+ it 'should return an aggregate activity feed with the items correctly ordered' do
18
+ add_items_to_feed('david', 5, true)
19
+
20
+ feed = ActivityFeed.feed('david', 1, true)
21
+ feed.length.should == 5
22
+ feed[0].to_i.should == 5
23
+ feed[4].to_i.should == 1
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#feed_between_timestamps' do
29
+ describe 'without aggregation' do
30
+ it 'should return activity feed items between the starting and ending timestamps' do
31
+ Timecop.travel(Time.local(2012, 6, 19, 4, 0, 0))
32
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i)
33
+ Timecop.travel(Time.local(2012, 6, 19, 4, 30, 0))
34
+ ActivityFeed.update_item('david', 2, DateTime.now.to_i)
35
+ Timecop.travel(Time.local(2012, 6, 19, 5, 30, 0))
36
+ ActivityFeed.update_item('david', 3, DateTime.now.to_i)
37
+ Timecop.travel(Time.local(2012, 6, 19, 6, 37, 0))
38
+ ActivityFeed.update_item('david', 4, DateTime.now.to_i)
39
+ Timecop.travel(Time.local(2012, 6, 19, 8, 17, 0))
40
+ ActivityFeed.update_item('david', 5, DateTime.now.to_i)
41
+ Timecop.return
42
+
43
+ feed = ActivityFeed.feed_between_timestamps('david', Time.local(2012, 6, 19, 4, 43, 0).to_i, Time.local(2012, 6, 19, 8, 16, 0).to_i)
44
+ feed.length.should == 2
45
+ feed[0].to_i.should == 4
46
+ feed[1].to_i.should == 3
47
+ end
48
+ end
49
+
50
+ describe 'with aggregation' do
51
+ it 'should return activity feed items between the starting and ending timestamps' do
52
+ Timecop.travel(Time.local(2012, 6, 19, 4, 0, 0))
53
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i, true)
54
+ Timecop.travel(Time.local(2012, 6, 19, 4, 30, 0))
55
+ ActivityFeed.update_item('david', 2, DateTime.now.to_i, true)
56
+ Timecop.travel(Time.local(2012, 6, 19, 5, 30, 0))
57
+ ActivityFeed.update_item('david', 3, DateTime.now.to_i, true)
58
+ Timecop.travel(Time.local(2012, 6, 19, 6, 37, 0))
59
+ ActivityFeed.update_item('david', 4, DateTime.now.to_i, true)
60
+ Timecop.travel(Time.local(2012, 6, 19, 8, 17, 0))
61
+ ActivityFeed.update_item('david', 5, DateTime.now.to_i, true)
62
+ Timecop.return
63
+
64
+ feed = ActivityFeed.feed_between_timestamps('david', Time.local(2012, 6, 19, 4, 43, 0).to_i, Time.local(2012, 6, 19, 8, 16, 0).to_i, true)
65
+ feed.length.should == 2
66
+ feed[0].to_i.should == 4
67
+ feed[1].to_i.should == 3
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#total_pages_in_feed' do
73
+ describe 'without aggregation' do
74
+ it 'should return the correct number of pages in the activity feed' do
75
+ add_items_to_feed('david', Leaderboard::DEFAULT_PAGE_SIZE + 1)
76
+
77
+ ActivityFeed.total_pages_in_feed('david').should == 2
78
+ end
79
+ end
80
+
81
+ describe 'with aggregation' do
82
+ it 'should return the correct number of pages in the aggregate activity feed' do
83
+ add_items_to_feed('david', Leaderboard::DEFAULT_PAGE_SIZE + 1, true)
84
+
85
+ ActivityFeed.total_pages_in_feed('david', true).should == 2
86
+ end
87
+ end
88
+
89
+ describe 'changing page_size parameter' do
90
+ it 'should return the correct number of pages in the activity feed' do
91
+ add_items_to_feed('david', 25)
92
+
93
+ ActivityFeed.total_pages_in_feed('david', false, 4).should == 7
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#remove_feeds' do
99
+ it 'should remove the activity feeds for a given user ID' do
100
+ add_items_to_feed('david', Leaderboard::DEFAULT_PAGE_SIZE + 1, true)
101
+
102
+ ActivityFeed.total_items_in_feed('david').should == Leaderboard::DEFAULT_PAGE_SIZE + 1
103
+ ActivityFeed.total_items_in_feed('david', true).should == Leaderboard::DEFAULT_PAGE_SIZE + 1
104
+
105
+ ActivityFeed.remove_feeds('david')
106
+
107
+ ActivityFeed.total_items_in_feed('david').should == 0
108
+ ActivityFeed.total_items_in_feed('david', true).should == 0
109
+ end
110
+ end
111
+
112
+ describe '#total_items_in_feed' do
113
+ describe 'without aggregation' do
114
+ it 'should return the correct number of items in the activity feed' do
115
+ add_items_to_feed('david', Leaderboard::DEFAULT_PAGE_SIZE + 1)
116
+
117
+ ActivityFeed.total_items_in_feed('david').should == Leaderboard::DEFAULT_PAGE_SIZE + 1
118
+ end
119
+ end
120
+
121
+ describe 'with aggregation' do
122
+ it 'should return the correct number of items in the aggregate activity feed' do
123
+ add_items_to_feed('david', Leaderboard::DEFAULT_PAGE_SIZE + 1, true)
124
+
125
+ ActivityFeed.total_items_in_feed('david', true).should == Leaderboard::DEFAULT_PAGE_SIZE + 1
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#trim_feed' do
131
+ describe 'without aggregation' do
132
+ it 'should trim activity feed items between the starting and ending timestamps' do
133
+ t1 = Timecop.travel(Time.local(2012, 6, 19, 4, 0, 0))
134
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i)
135
+ t2 = Timecop.travel(Time.local(2012, 6, 19, 4, 30, 0))
136
+ ActivityFeed.update_item('david', 2, DateTime.now.to_i)
137
+ t3 = Timecop.travel(Time.local(2012, 6, 19, 5, 30, 0))
138
+ ActivityFeed.update_item('david', 3, DateTime.now.to_i)
139
+ t4 = Timecop.travel(Time.local(2012, 6, 19, 6, 37, 0))
140
+ ActivityFeed.update_item('david', 4, DateTime.now.to_i)
141
+ t5 = Timecop.travel(Time.local(2012, 6, 19, 8, 17, 0))
142
+ ActivityFeed.update_item('david', 5, DateTime.now.to_i)
143
+ Timecop.return
144
+
145
+ ActivityFeed.trim_feed('david', Time.local(2012, 6, 19, 4, 29, 0).to_i, Time.local(2012, 6, 19, 8, 16, 0).to_i)
146
+ feed = ActivityFeed.feed('david', 1)
147
+ feed.length.should == 2
148
+ feed[0].to_i.should == 5
149
+ feed[1].to_i.should == 1
150
+ end
151
+ end
152
+
153
+ describe 'with aggregation' do
154
+ it 'should trim activity feed items between the starting and ending timestamps' do
155
+ t1 = Timecop.travel(Time.local(2012, 6, 19, 4, 0, 0))
156
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i, true)
157
+ t2 = Timecop.travel(Time.local(2012, 6, 19, 4, 30, 0))
158
+ ActivityFeed.update_item('david', 2, DateTime.now.to_i, true)
159
+ t3 = Timecop.travel(Time.local(2012, 6, 19, 5, 30, 0))
160
+ ActivityFeed.update_item('david', 3, DateTime.now.to_i, true)
161
+ t4 = Timecop.travel(Time.local(2012, 6, 19, 6, 37, 0))
162
+ ActivityFeed.update_item('david', 4, DateTime.now.to_i, true)
163
+ t5 = Timecop.travel(Time.local(2012, 6, 19, 8, 17, 0))
164
+ ActivityFeed.update_item('david', 5, DateTime.now.to_i, true)
165
+ Timecop.return
166
+
167
+ ActivityFeed.trim_feed('david', Time.local(2012, 6, 19, 4, 29, 0).to_i, Time.local(2012, 6, 19, 8, 16, 0).to_i, true)
168
+ feed = ActivityFeed.feed('david', 1, true)
169
+ feed.length.should == 2
170
+ feed[0].to_i.should == 5
171
+ feed[1].to_i.should == 1
172
+ end
173
+ end
174
+ end
175
+
176
+ describe 'ORM or ODM loading' do
177
+ describe 'ActiveRecord' do
178
+ it 'should be able to load an item via ActiveRecord when requesting a feed' do
179
+ ActivityFeed.item_loader = Proc.new do |id|
180
+ ActivityFeed::ActiveRecord::Item.find(id)
181
+ end
182
+
183
+ feed = ActivityFeed.feed('david', 1)
184
+ feed.length.should == 0
185
+
186
+ item = ActivityFeed::ActiveRecord::Item.create(
187
+ :user_id => 'david',
188
+ :nickname => 'David Czarnecki',
189
+ :type => 'some_activity',
190
+ :title => 'Great activity',
191
+ :body => 'This is text for the feed item'
192
+ )
193
+
194
+ feed = ActivityFeed.feed('david', 1)
195
+ feed.length.should == 1
196
+ feed[0].should == item
197
+ end
198
+ end
199
+
200
+ describe 'Mongoid' do
201
+ it 'should be able to load an item via Mongoid when requesting a feed' do
202
+ ActivityFeed.item_loader = Proc.new { |id| ActivityFeed::Mongoid::Item.find(id) }
203
+
204
+ feed = ActivityFeed.feed('david', 1)
205
+ feed.length.should == 0
206
+
207
+ item = ActivityFeed::Mongoid::Item.create(
208
+ :user_id => 'david',
209
+ :nickname => 'David Czarnecki',
210
+ :type => 'some_activity',
211
+ :title => 'Great activity',
212
+ :text => 'This is text for the feed item',
213
+ :url => 'http://url.com'
214
+ )
215
+
216
+ feed = ActivityFeed.feed('david', 1)
217
+ feed.length.should == 1
218
+ feed[0].should == item
219
+ end
220
+ end
221
+
222
+ describe 'item_loader exception handling' do
223
+ it 'should call the item_loader_exception_handler if it is set and there is an exception loading an activity feed item' do
224
+ ActivityFeed.item_loader = Proc.new do |id|
225
+ begin
226
+ ActivityFeed::Mongoid::Item.find(id)
227
+ rescue Mongoid::Errors::DocumentNotFound
228
+ end
229
+ end
230
+
231
+ ActivityFeed.update_item('david', '4fe4c5f3421aa9b89c000001', Time.now.to_i, false)
232
+ feed = ActivityFeed.feed('david', 1)
233
+ feed.length.should == 0
234
+ end
235
+
236
+ it 'should still load an activity feed, but call the item_loader_exception_handler if it is set and there is an exception loading an activity feed item' do
237
+ ActivityFeed.item_loader = Proc.new do |id|
238
+ begin
239
+ ActivityFeed::Mongoid::Item.find(id)
240
+ rescue Mongoid::Errors::DocumentNotFound
241
+ end
242
+ end
243
+
244
+ item = ActivityFeed::Mongoid::Item.create(
245
+ :user_id => 'david',
246
+ :nickname => 'David Czarnecki',
247
+ :type => 'some_activity',
248
+ :title => 'Great activity',
249
+ :text => 'This is text for the feed item',
250
+ :url => 'http://url.com'
251
+ )
252
+
253
+ ActivityFeed.update_item('david', '4fe4c5f3421aa9b89c000001', DateTime.now.to_i)
254
+
255
+ another_item = ActivityFeed::Mongoid::Item.create(
256
+ :user_id => 'david',
257
+ :nickname => 'David Czarnecki',
258
+ :type => 'some_activity',
259
+ :title => 'Great activity',
260
+ :text => 'This is more text for the feed item',
261
+ :url => 'http://url.com'
262
+ )
263
+
264
+ feed = ActivityFeed.feed('david', 1)
265
+
266
+ feed.length.should == 2
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'active_support/core_ext/date_time/conversions'
3
+
4
+ describe ActivityFeed::Item do
5
+ describe '#update_item' do
6
+ describe 'without aggregation' do
7
+ it 'should correctly build an activity feed' do
8
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
9
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i)
10
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_true
11
+ end
12
+ end
13
+
14
+ describe 'with aggregation' do
15
+ it 'should correctly build an activity feed with an aggregate activity_feed' do
16
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
17
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_false
18
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i, true)
19
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_true
20
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_true
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#aggregate_item' do
26
+ it 'should correctly add an item into an aggregate activity feed' do
27
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
28
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_false
29
+ ActivityFeed.aggregate_item('david', 1, DateTime.now.to_i)
30
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
31
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_true
32
+ end
33
+ end
34
+
35
+ describe '#remove_item' do
36
+ describe 'without aggregation' do
37
+ it 'should remove an item from an activity feed' do
38
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
39
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 0
40
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i)
41
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_true
42
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 1
43
+ ActivityFeed.remove_item('david', 1)
44
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 0
45
+ end
46
+ end
47
+
48
+ describe 'with aggregation' do
49
+ it 'should remove an item from an activity feed and the aggregate feed' do
50
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_false
51
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_false
52
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 0
53
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david', true)).should == 0
54
+ ActivityFeed.update_item('david', 1, DateTime.now.to_i, true)
55
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david')).should be_true
56
+ ActivityFeed.redis.exists(ActivityFeed.feed_key('david', true)).should be_true
57
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 1
58
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david', true)).should == 1
59
+ ActivityFeed.remove_item('david', 1)
60
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david')).should == 0
61
+ ActivityFeed.redis.zcard(ActivityFeed.feed_key('david', true)).should == 0
62
+ end
63
+ end
64
+ end
65
+ end