activity_feed 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.rdb
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.2-p290@activity_feed_gem --create
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.0.0
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in activity_feed.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 David Czarnecki
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,205 @@
1
+ # ActivityFeed
2
+
3
+ Activity feeds backed by Redis
4
+
5
+ ## Compatibility
6
+
7
+ The gem has been built and tested under Ruby 1.9.2-p290
8
+
9
+ ## Installation
10
+
11
+ `gem install activity_feed`
12
+
13
+ or:
14
+
15
+ `gem 'activity_feed'`
16
+
17
+ Make sure your redis server is running! Redis configuration is outside the scope of this README, but
18
+ check out the Redis documentation, http://redis.io/documentation.
19
+
20
+ ## Configuration
21
+
22
+ ```ruby
23
+ ActivityFeed.redis = Redis.new(:host => '127.0.0.1', :port => 6379)
24
+ ActivityFeed.namespace = 'activity'
25
+ ActivityFeed.key = 'feed'
26
+ ActivityFeed.persistence = :memory (or :active_record or :mongo_mapper)
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Make sure to set the Redis connection for use by the ActivityFeed classes.
32
+
33
+ ```ruby
34
+ $redis = Redis.new(:host => '127.0.0.1', :port => 6379)
35
+ ActivityFeed.redis = $redis
36
+ ```
37
+
38
+ ### Memory-backed persistence
39
+
40
+ ActivityFeed defaults to using memory-backed persistence, storing the full item as JSON in Redis.
41
+
42
+ ```ruby
43
+ ruby-1.9.2-p290 :001 > require 'redis'
44
+ => true
45
+ ruby-1.9.2-p290 :002 > $redis = Redis.new(:host => 'localhost', :port => 6379)
46
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
47
+ ruby-1.9.2-p290 :003 > require 'activity_feed'
48
+ => true
49
+ ruby-1.9.2-p290 :004 > ActivityFeed.redis = $redis
50
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
51
+ ruby-1.9.2-p290 :005 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text')
52
+ => #<ActivityFeed::Memory::Item:0x00000100ceaaa8 @attributes={:user_id=>1, :nickname=>"David Czarnecki", :type=>"activity-type", :text=>"Text"}, @user_id=1, @nickname="David Czarnecki", @type="activity-type", @text="Text">
53
+ ruby-1.9.2-p290 :006 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text')
54
+ => #<ActivityFeed::Memory::Item:0x000001022b0c48 @attributes={:user_id=>1, :nickname=>"David Czarnecki", :type=>"activity-type", :text=>"More text"}, @user_id=1, @nickname="David Czarnecki", @type="activity-type", @text="More text">
55
+ ruby-1.9.2-p290 :007 > feed = ActivityFeed::Feed.new(1)
56
+ => #<ActivityFeed::Feed:0x00000103023b78 @feederboard=#<Leaderboard:0x00000103023a88 @leaderboard_name="activity:feed:1", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>>>
57
+ ruby-1.9.2-p290 :008 > feed.page(1)
58
+ => [{"user_id"=>1, "nickname"=>"David Czarnecki", "type"=>"activity-type", "text"=>"More text"}, {"user_id"=>1, "nickname"=>"David Czarnecki", "type"=>"activity-type", "text"=>"Text"}]
59
+ ruby-1.9.2-p290 :009 >
60
+ ```
61
+
62
+ ### ActiveRecord persistence
63
+
64
+ ActivityFeed can also use ActiveRecord to persist the items to more durable storage while
65
+ keeping the IDs for the activity feed items in Redis. You can set this using:
66
+
67
+ ```ruby
68
+ ActivityFeed.persistence = :active_record
69
+ ```
70
+
71
+ Example:
72
+
73
+ ```ruby
74
+ ruby-1.9.2-p290 :001 > require 'active_record'
75
+ => true
76
+ ruby-1.9.2-p290 :002 >
77
+ ruby-1.9.2-p290 :003 > ActiveRecord::Base.establish_connection(
78
+ ruby-1.9.2-p290 :004 > :adapter => "sqlite3",
79
+ ruby-1.9.2-p290 :005 > :database => ":memory:"
80
+ ruby-1.9.2-p290 :006?> )
81
+ => #<ActiveRecord::ConnectionAdapters::ConnectionPool:0x00000101329cc0 @spec=#<ActiveRecord::Base::ConnectionSpecification:0x00000101329d38 @config={:adapter=>"sqlite3", :database=>":memory:"}, @adapter_method="sqlite3_connection">, @reserved_connections={}, @connection_mutex=#<Monitor:0x00000101329bd0 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x00000101329b80>>, @queue=#<MonitorMixin::ConditionVariable:0x00000101329b30 @monitor=#<Monitor:0x00000101329bd0 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x00000101329b80>>, @cond=#<ConditionVariable:0x00000101329b08 @waiters=[], @waiters_mutex=#<Mutex:0x00000101329978>>>, @timeout=5, @size=5, @connections=[], @checked_out=[], @automatic_reconnect=true, @tables={}, @visitor=nil, @columns={}, @columns_hash={}, @column_defaults={}, @primary_keys={}>
82
+ ruby-1.9.2-p290 :007 >
83
+ ruby-1.9.2-p290 :008 > ActiveRecord::Migration.verbose = false
84
+ => false
85
+ ruby-1.9.2-p290 :009 >
86
+ ruby-1.9.2-p290 :010 > ActiveRecord::Schema.define do
87
+ ruby-1.9.2-p290 :011 > create_table :activity_feed_items, :force => true do |t|
88
+ ruby-1.9.2-p290 :012 > t.integer :user_id
89
+ ruby-1.9.2-p290 :013?> t.string :nickname
90
+ ruby-1.9.2-p290 :014?> t.string :type
91
+ ruby-1.9.2-p290 :015?> t.string :title
92
+ ruby-1.9.2-p290 :016?> t.text :text
93
+ ruby-1.9.2-p290 :017?> t.string :url
94
+ ruby-1.9.2-p290 :018?> t.string :icon
95
+ ruby-1.9.2-p290 :019?> t.boolean :sticky
96
+ ruby-1.9.2-p290 :020?>
97
+ ruby-1.9.2-p290 :021 > t.timestamps
98
+ ruby-1.9.2-p290 :022?> end
99
+ ruby-1.9.2-p290 :023?>
100
+ ruby-1.9.2-p290 :024 > add_index :activity_feed_items, :user_id
101
+ ruby-1.9.2-p290 :025?> end
102
+ => nil
103
+ ruby-1.9.2-p290 :026 >
104
+ ruby-1.9.2-p290 :027 > require 'redis'
105
+ => true
106
+ ruby-1.9.2-p290 :028 > $redis = Redis.new(:host => 'localhost', :port => 6379)
107
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
108
+ ruby-1.9.2-p290 :029 > require 'activity_feed'
109
+ => true
110
+ ruby-1.9.2-p290 :030 > ActivityFeed.redis = $redis
111
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
112
+ ruby-1.9.2-p290 :031 > ActivityFeed.persistence = :active_record
113
+ => :active_record
114
+ ruby-1.9.2-p290 :032 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text')
115
+ => #<ActivityFeed::ActiveRecord::Item id: 1, user_id: 1, nickname: "David Czarnecki", type: nil, title: nil, text: "Text", url: nil, icon: nil, sticky: nil, created_at: "2011-09-14 15:08:22", updated_at: "2011-09-14 15:08:22">
116
+ ruby-1.9.2-p290 :033 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text')
117
+ => #<ActivityFeed::ActiveRecord::Item id: 2, user_id: 1, nickname: "David Czarnecki", type: nil, title: nil, text: "More text", url: nil, icon: nil, sticky: nil, created_at: "2011-09-14 15:08:25", updated_at: "2011-09-14 15:08:25">
118
+ ruby-1.9.2-p290 :034 > feed = ActivityFeed::Feed.new(1)
119
+ => #<ActivityFeed::Feed:0x000001030f1898 @feederboard=#<Leaderboard:0x000001030f1578 @leaderboard_name="activity:feed:1", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>>>
120
+ ruby-1.9.2-p290 :035 > feed.page(1)
121
+ => [#<ActivityFeed::ActiveRecord::Item id: 2, user_id: 1, nickname: "David Czarnecki", type: nil, title: nil, text: "More text", url: nil, icon: nil, sticky: nil, created_at: "2011-09-14 15:08:25", updated_at: "2011-09-14 15:08:25">, #<ActivityFeed::ActiveRecord::Item id: 1, user_id: 1, nickname: "David Czarnecki", type: nil, title: nil, text: "Text", url: nil, icon: nil, sticky: nil, created_at: "2011-09-14 15:08:22", updated_at: "2011-09-14 15:08:22">]
122
+ ruby-1.9.2-p290 :036 >
123
+ ```
124
+
125
+ ### MongoMapper persistence
126
+
127
+ ActivityFeed can also use MongoMapper to persist the items to more durable storage while
128
+ keeping the IDs for the activity feed items in Redis. You can set this using:
129
+
130
+ ```ruby
131
+ ActivityFeed.persistence = :mongo_mapper
132
+ ```
133
+
134
+ Make sure MongoMapper is configured correctly before setting this option.
135
+ If using Activity Feed outside of Rails, you can do:
136
+
137
+ ```ruby
138
+ MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
139
+ MongoMapper.database = 'activity_feeds_production'
140
+ ```
141
+
142
+ ```ruby
143
+ ruby-1.9.2-p290 :001 > require 'mongo_mapper'
144
+ => true
145
+ ruby-1.9.2-p290 :002 > MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
146
+ => #<Mongo::Connection:0x00000100c7d520 @host_to_try=["localhost", 27017], @port=nil, @host=nil, @slave_ok=nil, @auths=[], @id_lock=#<Mutex:0x00000100c7d480>, @pool_size=1, @timeout=5.0, @op_timeout=nil, @connection_mutex=#<Mutex:0x00000100c7d458>, @safe=false, @safe_mutexes={#<TCPSocket:(closed)>=>#<Mutex:0x00000100c6f8a8>, #<TCPSocket:fd 5>=>#<Mutex:0x00000100c6db70>}, @queue=#<ConditionVariable:0x00000100c7d390 @waiters=[], @waiters_mutex=#<Mutex:0x00000100c7d340>>, @primary=["localhost", 27017], @primary_pool=#<Mongo::Pool:0x00000100c6f128 @connection=#<Mongo::Connection:0x00000100c7d520 ...>, @port=27017, @host="localhost", @size=1, @timeout=5.0, @connection_mutex=#<Mutex:0x00000100c6f100>, @queue=#<ConditionVariable:0x00000100c6f0b0 @waiters=[], @waiters_mutex=#<Mutex:0x00000100c6f060>>, @socket_ops={#<TCPSocket:fd 5>=>[]}, @sockets=[#<TCPSocket:fd 5>], @pids={#<TCPSocket:fd 5>=>61344}, @checked_out=[]>, @logger=nil, @read_primary=true>
147
+ ruby-1.9.2-p290 :003 > MongoMapper.database = 'activity_feed_gem_test'
148
+ => "activity_feed_gem_test"
149
+ ruby-1.9.2-p290 :004 > require 'redis'
150
+ => true
151
+ ruby-1.9.2-p290 :005 > $redis = Redis.new(:host => 'localhost', :port => 6379)
152
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
153
+ ruby-1.9.2-p290 :006 > require 'activity_feed'
154
+ => true
155
+ ruby-1.9.2-p290 :007 > ActivityFeed.redis = $redis
156
+ => #<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>
157
+ ruby-1.9.2-p290 :008 > ActivityFeed.persistence = :mongo_mapper
158
+ => :mongo_mapper
159
+ ruby-1.9.2-p290 :009 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text')
160
+ => #<ActivityFeed::MongoMapper::Item _id: BSON::ObjectId('4e70dcc512dac1efa0000001'), created_at: 2011-09-14 16:56:37 UTC, nickname: "David Czarnecki", text: "Text", type: "activity-type", updated_at: 2011-09-14 16:56:37 UTC, user_id: 1>
161
+ ruby-1.9.2-p290 :010 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text')
162
+ => #<ActivityFeed::MongoMapper::Item _id: BSON::ObjectId('4e70dcc512dac1efa0000003'), created_at: 2011-09-14 16:56:37 UTC, nickname: "David Czarnecki", text: "More text", type: "activity-type", updated_at: 2011-09-14 16:56:37 UTC, user_id: 1>
163
+ ruby-1.9.2-p290 :011 > feed = ActivityFeed::Feed.new(1)
164
+ => #<ActivityFeed::Feed:0x00000100c583d8 @feederboard=#<Leaderboard:0x00000100c58298 @leaderboard_name="activity:feed:1", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.12)>>>
165
+ ruby-1.9.2-p290 :012 > feed.page(1)
166
+ => [#<ActivityFeed::MongoMapper::Item _id: BSON::ObjectId('4e70dcc512dac1efa0000003'), created_at: 2011-09-14 16:56:37 UTC, nickname: "David Czarnecki", text: "More text", type: "activity-type", updated_at: 2011-09-14 16:56:37 UTC, user_id: 1>, #<ActivityFeed::MongoMapper::Item _id: BSON::ObjectId('4e70dcc512dac1efa0000001'), created_at: 2011-09-14 16:56:37 UTC, nickname: "David Czarnecki", text: "Text", type: "activity-type", updated_at: 2011-09-14 16:56:37 UTC, user_id: 1>]
167
+ ruby-1.9.2-p290 :013 >
168
+ ```
169
+
170
+ ### Custom persistence
171
+
172
+ ActivityFeed can also use a custom class to do more customization. You can set this using:
173
+
174
+ ```ruby
175
+ ActivityFeed.persistence = :custom
176
+ ```
177
+
178
+ This will try to load the following class:
179
+
180
+ ```ruby
181
+ ActivityFeed::Custom::Item
182
+ ```
183
+
184
+ If you set persistence to be `:foo`, it would try to load the following class:
185
+
186
+ ```ruby
187
+ ActivityFeed::Foo::Item
188
+ ```
189
+
190
+ The custom class should implement a find(item_or_item_id) method that does "the right thing".
191
+ Consult the specs to see this working if you have questions.
192
+
193
+ ## Contributing to Activity Feed
194
+
195
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
196
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
197
+ * Fork the project
198
+ * Start a feature/bugfix branch
199
+ * Commit and push until you are happy with your contribution
200
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
201
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
202
+
203
+ ## Copyright
204
+
205
+ Copyright (c) 2011 David Czarnecki. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = 'spec/**/*_spec.rb'
9
+ spec.rspec_opts = ['--backtrace']
10
+ # spec.ruby_opts = ['-w']
11
+ end
12
+
13
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'activity_feed/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'activity_feed'
7
+ s.version = ActivityFeed::VERSION
8
+ s.authors = ['David Czarnecki']
9
+ s.email = ['dczarnecki@agoragames.com']
10
+ s.homepage = 'https://github.com/agoragames/activity_feed'
11
+ s.summary = %q{Activity feeds backed by Redis}
12
+ s.description = %q{Activity feeds backed by Redis}
13
+
14
+ s.rubyforge_project = 'activity_feed'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_development_dependency('rake')
23
+ s.add_development_dependency('rspec')
24
+ s.add_development_dependency('database_cleaner')
25
+ s.add_development_dependency('fabrication')
26
+
27
+ s.add_development_dependency('mongo_mapper')
28
+ s.add_development_dependency('mongo_ext')
29
+ s.add_development_dependency('bson_ext')
30
+
31
+ s.add_development_dependency('activerecord')
32
+ s.add_development_dependency('sqlite3')
33
+
34
+ s.add_dependency('activesupport')
35
+ s.add_dependency('i18n')
36
+
37
+ s.add_dependency('json')
38
+ s.add_dependency('redis')
39
+ s.add_dependency('leaderboard')
40
+ end
@@ -0,0 +1,56 @@
1
+ require 'activity_feed/version'
2
+ require 'activity_feed/feed'
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+ require 'active_support/inflector'
5
+
6
+ require 'redis'
7
+
8
+ module ActivityFeed
9
+ mattr_accessor :redis
10
+ mattr_accessor :namespace
11
+ mattr_accessor :key
12
+ mattr_accessor :persistence
13
+
14
+ def self.persistence=(type = :memory)
15
+ @@persistence_type = type
16
+
17
+ case type
18
+ when :memory
19
+ require 'activity_feed/memory/item'
20
+ klazz = ActivityFeed::Memory::Item
21
+ when :mongo_mapper
22
+ require 'activity_feed/mongo_mapper/item'
23
+ klazz = ActivityFeed::MongoMapper::Item
24
+ when :active_record
25
+ require 'activity_feed/active_record/item'
26
+ klazz = ActivityFeed::ActiveRecord::Item
27
+ else
28
+ klazz = "ActivityFeed::#{type.to_s.classify}::Item".constantize
29
+ end
30
+
31
+ @@persistence = klazz
32
+ end
33
+
34
+ def self.create_item(attributes)
35
+ item = @@persistence.new(attributes)
36
+ item.save
37
+ item
38
+ end
39
+
40
+ def self.load_item(item_or_item_id)
41
+ case @@persistence_type
42
+ when :memory
43
+ JSON.parse(item_or_item_id)
44
+ when :mongo_mapper
45
+ ActivityFeed::MongoMapper::Item.find(item_or_item_id)
46
+ when :active_record
47
+ ActivityFeed::ActiveRecord::Item.find(item_or_item_id)
48
+ else
49
+ @@persistence.find(item_or_item_id)
50
+ end
51
+ end
52
+
53
+ self.namespace = 'activity'
54
+ self.key = 'feed'
55
+ self.persistence = :memory
56
+ end
@@ -0,0 +1,17 @@
1
+ require 'active_record'
2
+
3
+ module ActivityFeed
4
+ module ActiveRecord
5
+ class Item < ::ActiveRecord::Base
6
+ set_table_name 'activity_feed_items'
7
+
8
+ after_save :update_redis
9
+
10
+ private
11
+
12
+ def update_redis
13
+ ActivityFeed.redis.zadd("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{self.user_id}", self.created_at.to_i, self.id)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'leaderboard'
2
+
3
+ module ActivityFeed
4
+ class Feed
5
+ def initialize(user_id)
6
+ @feederboard = Leaderboard.new("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}", Leaderboard::DEFAULT_OPTIONS, {:redis_connection => ActivityFeed.redis})
7
+ end
8
+
9
+ def page(page)
10
+ feed_items = []
11
+ @feederboard.leaders(page).each do |feed_item|
12
+ feed_items << ActivityFeed.load_item(feed_item[:member])
13
+ end
14
+
15
+ feed_items
16
+ end
17
+
18
+ def total_pages
19
+ @feederboard.total_pages
20
+ end
21
+
22
+ def total_items
23
+ @feederboard.total_members
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/date_time/conversions'
3
+ require 'json'
4
+
5
+ module ActivityFeed
6
+ module Memory
7
+ class Item
8
+ attr_accessor :user_id
9
+ attr_accessor :nickname
10
+ attr_accessor :type
11
+ attr_accessor :title
12
+ attr_accessor :text
13
+ attr_accessor :url
14
+ attr_accessor :icon
15
+ attr_accessor :sticky
16
+
17
+ def initialize(attributes = {})
18
+ @attributes = attributes
19
+
20
+ attributes.each do |key,value|
21
+ self.send("#{key}=", value)
22
+ end
23
+ end
24
+
25
+ def save
26
+ raise 'user_id MUST be defined in the attributes' if user_id.blank?
27
+
28
+ ActivityFeed.redis.zadd("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{self.user_id}", DateTime.now.to_i, @attributes.to_json)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ require 'mongo_mapper'
2
+
3
+ module ActivityFeed
4
+ module MongoMapper
5
+ class Item
6
+ include ::MongoMapper::Document
7
+
8
+ key :user_id, Integer, :required => true, :numeric => true
9
+ key :nickname, String
10
+ key :type, String
11
+ key :title, String
12
+ key :text, String
13
+ key :url, String
14
+ key :icon, String
15
+ key :sticky, Boolean
16
+
17
+ timestamps!
18
+
19
+ self.ensure_index(:user_id)
20
+
21
+ after_save :update_redis
22
+
23
+ private
24
+
25
+ def update_redis
26
+ ActivityFeed.redis.zadd("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{self.user_id}", self.created_at.to_i, self.id)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module ActivityFeed
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,32 @@
1
+ module ActivityFeed
2
+ module Custom
3
+ class Item
4
+ attr_accessor :user_id
5
+ attr_accessor :nickname
6
+ attr_accessor :type
7
+ attr_accessor :title
8
+ attr_accessor :text
9
+ attr_accessor :url
10
+ attr_accessor :icon
11
+ attr_accessor :sticky
12
+
13
+ def initialize(attributes = {})
14
+ @attributes = attributes
15
+
16
+ attributes.each do |key,value|
17
+ self.send("#{key}=", value)
18
+ end
19
+ end
20
+
21
+ def self.find(item)
22
+ JSON.parse(item)
23
+ end
24
+
25
+ def save
26
+ raise 'user_id MUST be defined in the attributes' if user_id.blank?
27
+
28
+ ActivityFeed.redis.zadd("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{self.user_id}", DateTime.now.to_i, @attributes.to_json)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActivityFeed do
4
+ it 'should have defaults set for :namespace and :key' do
5
+ ActivityFeed.namespace.should eql('activity')
6
+ ActivityFeed.key.should eql('feed')
7
+ ActivityFeed.persistence = :memory
8
+ ActivityFeed.persistence.should be(ActivityFeed::Memory::Item)
9
+ end
10
+
11
+ describe 'creating' do
12
+ it 'should allow you to create a new item using :memory' do
13
+ user_id = 1
14
+ ActivityFeed.persistence = :memory
15
+
16
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(0)
17
+ ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
18
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(1)
19
+ end
20
+
21
+ it 'should allow you to create a new item using :mongo_mapper' do
22
+ user_id = 1
23
+ ActivityFeed.persistence = :mongo_mapper
24
+
25
+ ActivityFeed::MongoMapper::Item.count.should be(0)
26
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(0)
27
+ ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
28
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(1)
29
+ ActivityFeed::MongoMapper::Item.count.should be(1)
30
+ end
31
+
32
+ it 'should allow you to create a new item using :active_record' do
33
+ user_id = 1
34
+ ActivityFeed.persistence = :active_record
35
+
36
+ ActivityFeed::ActiveRecord::Item.count.should be(0)
37
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(0)
38
+ ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
39
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(1)
40
+ ActivityFeed::ActiveRecord::Item.count.should be(1)
41
+ end
42
+ end
43
+
44
+ describe 'loading' do
45
+ it 'should allow you to load an item using :memory' do
46
+ user_id = 1
47
+ ActivityFeed.persistence = :memory
48
+
49
+ item = ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
50
+ loaded_item = ActivityFeed.load_item(item.to_json)
51
+ loaded_item.should == JSON.parse(item.to_json)
52
+ end
53
+
54
+ it 'should allow you to load an item using :mongo_mapper' do
55
+ user_id = 1
56
+ ActivityFeed.persistence = :mongo_mapper
57
+
58
+ item = ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
59
+ loaded_item = ActivityFeed.load_item(item.id)
60
+ loaded_item.should == item
61
+ end
62
+
63
+ it 'should allow you to load an item using :active_record' do
64
+ user_id = 1
65
+ ActivityFeed.persistence = :active_record
66
+
67
+ item = ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
68
+ loaded_item = ActivityFeed.load_item(item.id)
69
+ loaded_item.should == item
70
+ end
71
+ end
72
+
73
+ describe 'custom persistence' do
74
+ it 'should allow you to define a custom persistence handler class' do
75
+ ActivityFeed.persistence = :custom
76
+ end
77
+
78
+ it 'should allow you to create a new item using a custom persistence handler class' do
79
+ user_id = 1
80
+ ActivityFeed.persistence = :custom
81
+
82
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(0)
83
+ ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
84
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{user_id}").should be(1)
85
+ end
86
+
87
+ it 'should allow you to load an item using a custom persistence handler class' do
88
+ user_id = 1
89
+ ActivityFeed.persistence = :custom
90
+
91
+ item = ActivityFeed.create_item(:user_id => user_id, :text => 'This is text for my activity feed')
92
+ loaded_item = ActivityFeed.load_item(item.to_json)
93
+ loaded_item.should == JSON.parse(item.to_json)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,32 @@
1
+ Fabricator(ActivityFeed::ActiveRecord::Item) do
2
+ user_id { sequence(:user_id) }
3
+ nickname { sequence(:nickname) { |i| "nickname_#{i}" } }
4
+ type { 'activity' }
5
+ title { 'item title' }
6
+ text { 'feed item' }
7
+ url { 'http://url' }
8
+ icon { 'http://icon' }
9
+ sticky { false }
10
+ end
11
+
12
+ Fabricator(ActivityFeed::Memory::Item) do
13
+ user_id { sequence(:user_id) }
14
+ nickname { sequence(:nickname) { |i| "nickname_#{i}" } }
15
+ type { 'activity' }
16
+ title { 'item title' }
17
+ text { 'feed item' }
18
+ url { 'http://url' }
19
+ icon { 'http://icon' }
20
+ sticky { false }
21
+ end
22
+
23
+ Fabricator(ActivityFeed::MongoMapper::Item) do
24
+ user_id { sequence(:user_id) }
25
+ nickname { sequence(:nickname) { |i| "nickname_#{i}" } }
26
+ type { 'activity' }
27
+ title { 'item title' }
28
+ text { 'feed item' }
29
+ url { 'http://url' }
30
+ icon { 'http://icon' }
31
+ sticky { false }
32
+ end
data/spec/feed_spec.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActivityFeed::Feed do
4
+ it 'should pull up the correct list of ActivityFeed::Memory::Item when calling #page using :memory' do
5
+ 1.upto(5) do |index|
6
+ item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}")
7
+ end
8
+
9
+ feed = ActivityFeed::Feed.new(1)
10
+ feed.page(1).size.should be(5)
11
+ end
12
+
13
+ it 'should pull up the correct list of ActivityFeed::MongoMapper::Item when calling #page using :mongo_mapper' do
14
+ ActivityFeed.persistence = :mongo_mapper
15
+ ActivityFeed::MongoMapper::Item.count.should be(0)
16
+ 1.upto(5) do |index|
17
+ item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}")
18
+ end
19
+ ActivityFeed::MongoMapper::Item.count.should be(5)
20
+
21
+ feed = ActivityFeed::Feed.new(1)
22
+ feed.page(1).size.should be(5)
23
+ end
24
+
25
+ it 'should return the correct number for #total_items' do
26
+ 1.upto(3) do |index|
27
+ item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}")
28
+ end
29
+
30
+ feed = ActivityFeed::Feed.new(1)
31
+ feed.total_items.should be(3)
32
+ end
33
+
34
+ it 'should return the correct number for #total_pages' do
35
+ 1.upto(Leaderboard::DEFAULT_PAGE_SIZE + 1) do |index|
36
+ item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}")
37
+ end
38
+
39
+ feed = ActivityFeed::Feed.new(1)
40
+ feed.total_pages.should be(2)
41
+ end
42
+
43
+ describe 'custom persistence' do
44
+ it 'should allow you to pull up the correct list of ActivityFeed::Custom::Item when calling #page using :custom' do
45
+ ActivityFeed.persistence = :custom
46
+ 1.upto(5) do |index|
47
+ item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}")
48
+ end
49
+
50
+ feed = ActivityFeed::Feed.new(1)
51
+ feed.page(1).size.should be(5)
52
+ end
53
+ end
54
+ end
data/spec/item_spec.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActivityFeed::Item' do
4
+ it 'should allow you to create a new Item' do
5
+ item = Fabricate.build(ActivityFeed.persistence)
6
+ item.save.should be_true
7
+ end
8
+
9
+ it 'should allow for a large amount of text' do
10
+ item = Fabricate.build(ActivityFeed.persistence, :text => '*' * 8192)
11
+ item.text.should eql('*' * 8192)
12
+ end
13
+
14
+ it 'should add the feed item ID to redis' do
15
+ item = Fabricate.build(ActivityFeed.persistence)
16
+
17
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{item.user_id}").should be(0)
18
+ item.save
19
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{item.user_id}").should be(1)
20
+ end
21
+
22
+ it 'should have default attributes for .title .url .icon and .sticky' do
23
+ item = Fabricate.build(ActivityFeed.persistence)
24
+
25
+ item.title.should eql('item title')
26
+ item.url.should eql('http://url')
27
+ item.icon.should eql('http://icon')
28
+ item.sticky.should be_false
29
+ end
30
+
31
+ it 'should not create a new item in Redis after saving, only on create' do
32
+ item = Fabricate.build(ActivityFeed::Memory::Item)
33
+
34
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{item.user_id}").should be(0)
35
+ item.save
36
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{item.user_id}").should be(1)
37
+
38
+ item.text = 'updated text'
39
+ item.save
40
+ ActivityFeed.redis.zcard("#{ActivityFeed.namespace}:#{ActivityFeed.key}:#{item.user_id}").should be(1)
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'redis'
4
+ require 'mongo_mapper'
5
+ require 'database_cleaner'
6
+ require 'fabrication'
7
+ require 'json'
8
+
9
+ $redis = Redis.new(:host => '127.0.0.1', :port => 6379)
10
+
11
+ # TODO: Move to spec/mongo_mapper
12
+ MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
13
+ MongoMapper.database = 'activity_feed_gem_test'
14
+
15
+ # TODO: Move to spec/active_record
16
+ require 'active_record'
17
+
18
+ ActiveRecord::Base.establish_connection(
19
+ :adapter => "sqlite3",
20
+ :database => ":memory:"
21
+ )
22
+
23
+ ActiveRecord::Migration.verbose = false
24
+
25
+ ActiveRecord::Schema.define do
26
+ create_table :activity_feed_items, :force => true do |t|
27
+ t.integer :user_id
28
+ t.string :nickname
29
+ t.string :type
30
+ t.string :title
31
+ t.text :text
32
+ t.string :url
33
+ t.string :icon
34
+ t.boolean :sticky
35
+
36
+ t.timestamps
37
+ end
38
+
39
+ add_index :activity_feed_items, :user_id
40
+ end
41
+
42
+ DatabaseCleaner[:active_record].strategy = :transaction
43
+ DatabaseCleaner[:mongo_mapper].strategy = :truncation
44
+
45
+ require 'activity_feed'
46
+
47
+ ActivityFeed.redis = $redis
48
+
49
+ require 'activity_feed/custom/item'
50
+
51
+ RSpec.configure do |config|
52
+ config.mock_with :rspec
53
+
54
+ config.before(:suite) do
55
+ DatabaseCleaner.strategy = :truncation
56
+ DatabaseCleaner.clean_with(:truncation)
57
+ $redis.flushdb
58
+ end
59
+
60
+ config.before(:each) do
61
+ ActivityFeed.persistence = :memory
62
+ DatabaseCleaner.start
63
+ DatabaseCleaner.clean
64
+ end
65
+
66
+ config.after(:each) do
67
+ DatabaseCleaner.clean
68
+ $redis.flushdb
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActivityFeed do
4
+ it "should be the correct version" do
5
+ ActivityFeed::VERSION.should == '1.0.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,234 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activity_feed
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - David Czarnecki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-10-05 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :development
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :development
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: database_cleaner
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: fabrication
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: mongo_mapper
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: mongo_ext
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id006
81
+ - !ruby/object:Gem::Dependency
82
+ name: bson_ext
83
+ prerelease: false
84
+ requirement: &id007 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ type: :development
91
+ version_requirements: *id007
92
+ - !ruby/object:Gem::Dependency
93
+ name: activerecord
94
+ prerelease: false
95
+ requirement: &id008 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ type: :development
102
+ version_requirements: *id008
103
+ - !ruby/object:Gem::Dependency
104
+ name: sqlite3
105
+ prerelease: false
106
+ requirement: &id009 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: "0"
112
+ type: :development
113
+ version_requirements: *id009
114
+ - !ruby/object:Gem::Dependency
115
+ name: activesupport
116
+ prerelease: false
117
+ requirement: &id010 !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: "0"
123
+ type: :runtime
124
+ version_requirements: *id010
125
+ - !ruby/object:Gem::Dependency
126
+ name: i18n
127
+ prerelease: false
128
+ requirement: &id011 !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: "0"
134
+ type: :runtime
135
+ version_requirements: *id011
136
+ - !ruby/object:Gem::Dependency
137
+ name: json
138
+ prerelease: false
139
+ requirement: &id012 !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: "0"
145
+ type: :runtime
146
+ version_requirements: *id012
147
+ - !ruby/object:Gem::Dependency
148
+ name: redis
149
+ prerelease: false
150
+ requirement: &id013 !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: "0"
156
+ type: :runtime
157
+ version_requirements: *id013
158
+ - !ruby/object:Gem::Dependency
159
+ name: leaderboard
160
+ prerelease: false
161
+ requirement: &id014 !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: "0"
167
+ type: :runtime
168
+ version_requirements: *id014
169
+ description: Activity feeds backed by Redis
170
+ email:
171
+ - dczarnecki@agoragames.com
172
+ executables: []
173
+
174
+ extensions: []
175
+
176
+ extra_rdoc_files: []
177
+
178
+ files:
179
+ - .gitignore
180
+ - .rvmrc
181
+ - CHANGELOG.markdown
182
+ - Gemfile
183
+ - LICENSE.txt
184
+ - README.markdown
185
+ - Rakefile
186
+ - activity_feed.gemspec
187
+ - lib/activity_feed.rb
188
+ - lib/activity_feed/active_record/item.rb
189
+ - lib/activity_feed/feed.rb
190
+ - lib/activity_feed/memory/item.rb
191
+ - lib/activity_feed/mongo_mapper/item.rb
192
+ - lib/activity_feed/version.rb
193
+ - spec/activity_feed/custom/item.rb
194
+ - spec/activity_feed_spec.rb
195
+ - spec/fabricators/item_fabricator.rb
196
+ - spec/feed_spec.rb
197
+ - spec/item_spec.rb
198
+ - spec/spec_helper.rb
199
+ - spec/version_spec.rb
200
+ homepage: https://github.com/agoragames/activity_feed
201
+ licenses: []
202
+
203
+ post_install_message:
204
+ rdoc_options: []
205
+
206
+ require_paths:
207
+ - lib
208
+ required_ruby_version: !ruby/object:Gem::Requirement
209
+ none: false
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: "0"
214
+ required_rubygems_version: !ruby/object:Gem::Requirement
215
+ none: false
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: "0"
220
+ requirements: []
221
+
222
+ rubyforge_project: activity_feed
223
+ rubygems_version: 1.8.11
224
+ signing_key:
225
+ specification_version: 3
226
+ summary: Activity feeds backed by Redis
227
+ test_files:
228
+ - spec/activity_feed/custom/item.rb
229
+ - spec/activity_feed_spec.rb
230
+ - spec/fabricators/item_fabricator.rb
231
+ - spec/feed_spec.rb
232
+ - spec/item_spec.rb
233
+ - spec/spec_helper.rb
234
+ - spec/version_spec.rb