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.
- data/.rvmrc +1 -0
- data/README.markdown +212 -174
- data/Rakefile +5 -1
- data/activity_feed.gemspec +8 -14
- data/lib/activity_feed/configuration.rb +65 -0
- data/lib/activity_feed/feed.rb +89 -20
- data/lib/activity_feed/item.rb +44 -0
- data/lib/activity_feed/utility.rb +25 -0
- data/lib/activity_feed/version.rb +1 -1
- data/lib/activity_feed.rb +10 -109
- data/spec/activity_feed/configuration_spec.rb +14 -0
- data/spec/activity_feed/feed_spec.rb +270 -0
- data/spec/activity_feed/item_spec.rb +65 -0
- data/spec/activity_feed/utility_spec.rb +24 -0
- data/spec/spec_helper.rb +28 -66
- data/spec/support/active_record.rb +38 -0
- data/spec/support/mongoid.rb +38 -0
- data/spec/version_spec.rb +2 -2
- metadata +24 -155
- data/lib/activity_feed/active_record/item.rb +0 -17
- data/lib/activity_feed/memory/item.rb +0 -36
- data/lib/activity_feed/mongo_mapper/item.rb +0 -30
- data/lib/activity_feed/mongoid/item.rb +0 -31
- data/lib/activity_feed/ohm/item.rb +0 -41
- data/spec/activity_feed/custom/item.rb +0 -36
- data/spec/activity_feed_spec.rb +0 -295
- data/spec/fabricators/item_fabricator.rb +0 -43
- data/spec/feed_spec.rb +0 -106
- data/spec/item_spec.rb +0 -55
data/lib/activity_feed/feed.rb
CHANGED
@@ -1,29 +1,98 @@
|
|
1
|
-
require 'leaderboard'
|
2
|
-
|
3
1
|
module ActivityFeed
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
54
|
+
feed.nil? ? [] : feed
|
19
55
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
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
|
data/lib/activity_feed.rb
CHANGED
@@ -1,113 +1,14 @@
|
|
1
|
-
require 'activity_feed/
|
1
|
+
require 'activity_feed/configuration'
|
2
|
+
require 'activity_feed/item'
|
2
3
|
require 'activity_feed/feed'
|
3
|
-
require '
|
4
|
-
require '
|
4
|
+
require 'activity_feed/utility'
|
5
|
+
require 'activity_feed/version'
|
5
6
|
|
6
|
-
require '
|
7
|
+
require 'leaderboard'
|
7
8
|
|
8
9
|
module ActivityFeed
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|