# ActivityFeed Activity feeds backed by Redis ## Compatibility The gem has been built and tested under Ruby 1.9.2-p290 and Ruby 1.9.3-p0 ## Installation `gem install activity_feed` or: `gem 'activity_feed'` Make sure your redis server is running! Redis configuration is outside the scope of this README, but check out the Redis documentation, http://redis.io/documentation. ## Configuration ```ruby ActivityFeed.redis = Redis.new(:host => '127.0.0.1', :port => 6379) ActivityFeed.namespace = 'activity' ActivityFeed.key = 'feed' ActivityFeed.persistence = :memory # (or :active_record or :mongo_mapper or :ohm) ActivityFeed.aggregate = true ActivityFeed.aggregate_key = 'aggregate' ``` ## Usage Make sure to set the Redis connection for use by the ActivityFeed classes. ```ruby $redis = Redis.new(:host => '127.0.0.1', :port => 6379) ActivityFeed.redis = $redis ``` ### Memory-backed persistence ActivityFeed defaults to using memory-backed persistence, storing the full item as JSON in Redis. ```ruby ruby-1.9.2-p290 :001 > require 'redis' => true ruby-1.9.2-p290 :002 > $redis = Redis.new(:host => 'localhost', :port => 6379) => # ruby-1.9.2-p290 :003 > require 'activity_feed' => true ruby-1.9.2-p290 :004 > ActivityFeed.redis = $redis => # ruby-1.9.2-p290 :005 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') => #1, :nickname=>"David Czarnecki", :type=>"activity-type", :text=>"Text"}, @user_id=1, @nickname="David Czarnecki", @type="activity-type", @text="Text"> ruby-1.9.2-p290 :006 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') => #1, :nickname=>"David Czarnecki", :type=>"activity-type", :text=>"More text"}, @user_id=1, @nickname="David Czarnecki", @type="activity-type", @text="More text"> ruby-1.9.2-p290 :007 > feed = ActivityFeed::Feed.new(1) => #>> ruby-1.9.2-p290 :008 > feed.page(1) => [{"user_id"=>1, "nickname"=>"David Czarnecki", "type"=>"activity-type", "text"=>"More text"}, {"user_id"=>1, "nickname"=>"David Czarnecki", "type"=>"activity-type", "text"=>"Text"}] ruby-1.9.2-p290 :009 > ``` ### ActiveRecord persistence ActivityFeed can also use ActiveRecord to persist the items to more durable storage while keeping the IDs for the activity feed items in Redis. You can set this using: ```ruby ActivityFeed.persistence = :active_record ``` Example: ```ruby ruby-1.9.2-p290 :001 > require 'active_record' => true ruby-1.9.2-p290 :002 > ruby-1.9.2-p290 :003 > ActiveRecord::Base.establish_connection( ruby-1.9.2-p290 :004 > :adapter => "sqlite3", ruby-1.9.2-p290 :005 > :database => ":memory:" ruby-1.9.2-p290 :006?> ) => #"sqlite3", :database=>":memory:"}, @adapter_method="sqlite3_connection">, @reserved_connections={}, @connection_mutex=#>, @queue=#>, @cond=#>>, @timeout=5, @size=5, @connections=[], @checked_out=[], @automatic_reconnect=true, @tables={}, @visitor=nil, @columns={}, @columns_hash={}, @column_defaults={}, @primary_keys={}> ruby-1.9.2-p290 :007 > ruby-1.9.2-p290 :008 > ActiveRecord::Migration.verbose = false => false ruby-1.9.2-p290 :009 > ruby-1.9.2-p290 :010 > ActiveRecord::Schema.define do ruby-1.9.2-p290 :011 > create_table :activity_feed_items, :force => true do |t| ruby-1.9.2-p290 :012 > t.integer :user_id ruby-1.9.2-p290 :013?> t.string :nickname ruby-1.9.2-p290 :014?> t.string :type ruby-1.9.2-p290 :015?> t.string :title ruby-1.9.2-p290 :016?> t.text :text ruby-1.9.2-p290 :017?> t.string :url ruby-1.9.2-p290 :018?> t.string :icon ruby-1.9.2-p290 :019?> t.boolean :sticky ruby-1.9.2-p290 :020?> ruby-1.9.2-p290 :021 > t.timestamps ruby-1.9.2-p290 :022?> end ruby-1.9.2-p290 :023?> ruby-1.9.2-p290 :024 > add_index :activity_feed_items, :user_id ruby-1.9.2-p290 :025?> end => nil ruby-1.9.2-p290 :026 > ruby-1.9.2-p290 :027 > require 'redis' => true ruby-1.9.2-p290 :028 > $redis = Redis.new(:host => 'localhost', :port => 6379) => # ruby-1.9.2-p290 :029 > require 'activity_feed' => true ruby-1.9.2-p290 :030 > ActivityFeed.redis = $redis => # ruby-1.9.2-p290 :031 > ActivityFeed.persistence = :active_record => :active_record ruby-1.9.2-p290 :032 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') => # ruby-1.9.2-p290 :033 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') => # ruby-1.9.2-p290 :034 > feed = ActivityFeed::Feed.new(1) => #>> ruby-1.9.2-p290 :035 > feed.page(1) => [#, #] ruby-1.9.2-p290 :036 > ``` ### MongoMapper persistence ActivityFeed can also use MongoMapper to persist the items to more durable storage while keeping the IDs for the activity feed items in Redis. You can set this using: ```ruby ActivityFeed.persistence = :mongo_mapper ``` Make sure MongoMapper is configured correctly before setting this option. If using Activity Feed outside of Rails, you can do: ```ruby MongoMapper.connection = Mongo::Connection.new('localhost', 27017) MongoMapper.database = 'activity_feeds_production' ``` ```ruby ruby-1.9.2-p290 :001 > require 'mongo_mapper' => true ruby-1.9.2-p290 :002 > MongoMapper.connection = Mongo::Connection.new('localhost', 27017) => #, @pool_size=1, @timeout=5.0, @op_timeout=nil, @connection_mutex=#, @safe=false, @safe_mutexes={#=>#, #=>#}, @queue=#>, @primary=["localhost", 27017], @primary_pool=#, @port=27017, @host="localhost", @size=1, @timeout=5.0, @connection_mutex=#, @queue=#>, @socket_ops={#=>[]}, @sockets=[#], @pids={#=>61344}, @checked_out=[]>, @logger=nil, @read_primary=true> ruby-1.9.2-p290 :003 > MongoMapper.database = 'activity_feed_gem_test' => "activity_feed_gem_test" ruby-1.9.2-p290 :004 > require 'redis' => true ruby-1.9.2-p290 :005 > $redis = Redis.new(:host => 'localhost', :port => 6379) => # ruby-1.9.2-p290 :006 > require 'activity_feed' => true ruby-1.9.2-p290 :007 > ActivityFeed.redis = $redis => # ruby-1.9.2-p290 :008 > ActivityFeed.persistence = :mongo_mapper => :mongo_mapper ruby-1.9.2-p290 :009 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') => # ruby-1.9.2-p290 :010 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') => # ruby-1.9.2-p290 :011 > feed = ActivityFeed::Feed.new(1) => #>> ruby-1.9.2-p290 :012 > feed.page(1) => [#, #] ruby-1.9.2-p290 :013 > ``` ### Ohm persistence ActivityFeed can also use Ohm to persist the items in Redis. You can set this using: ```ruby ruby-1.9.2-p290 :001 > require 'redis' => true ruby-1.9.2-p290 :002 > $redis = Redis.new(:host => 'localhost', :port => 6379) => # ruby-1.9.2-p290 :003 > require 'activity_feed' => true ruby-1.9.2-p290 :004 > ActivityFeed.redis = $redis => # ruby-1.9.2-p290 :005 > ActivityFeed.persistence = :ohm => :ohm ruby-1.9.2-p290 :006 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') => # ruby-1.9.2-p290 :007 > ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') => # ruby-1.9.2-p290 :008 > feed = ActivityFeed::Feed.new(1) => #>> ruby-1.9.2-p290 :009 > feed.page(1) => [#, #] ruby-1.9.2-p290 :010 > ``` ### Custom persistence ActivityFeed can also use a custom class to do more customization. You can set this using: ```ruby ActivityFeed.persistence = :custom ``` This will try to load the following class: ```ruby ActivityFeed::Custom::Item ``` If you set persistence to be `:foo`, it would try to load the following class: ```ruby ActivityFeed::Foo::Item ``` The custom class should implement a find(item_or_item_id) method that does "the right thing". Consult the specs to see this working if you have questions. ### Feeds and Aggregation Feeds You can access an activity feed in a couple of ways. ```ruby ActivityFeed.feed(user_id) # return an instance of ActivityFeed::Feed ``` or ```ruby ActivityFeed::Feed.new(user_id) ``` activity_feed uses the following key in adding the item to Redis: `ActivityFeed.namespace:ActivityFeed.key:self.user_id`. By default, activity_feed in the `create_item` call will also add the item in Redis to an aggregate feed using the key: `ActivityFeed.namespace:ActivityFeed.key:ActivityFeed.aggregate_key:self.user_id`. You can control aggregation globally by setting the ActivityFeed.aggregate property to either `true` or `false`. You can override the global aggregation setting on the `create_item` call by passing either `true` or `false` as the 2nd argument. Below is an example of an aggregate feed: ```ruby ruby-1.9.2-p290 :001 > require 'activity_feed' => true ruby-1.9.2-p290 :002 > require 'pp' => true ruby-1.9.2-p290 :003 > ruby-1.9.2-p290 :004 > $redis = Redis.new(:host => '127.0.0.1', :port => 6379) => # ruby-1.9.2-p290 :005 > ActivityFeed.redis = $redis => # ruby-1.9.2-p290 :006 > ActivityFeed.persistence = :ohm => :ohm ruby-1.9.2-p290 :007 > ruby-1.9.2-p290 :008 > 1.upto(5) do |index| ruby-1.9.2-p290 :009 > item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}") ruby-1.9.2-p290 :010?> sleep(1) ruby-1.9.2-p290 :011?> another_item = ActivityFeed.create_item(:user_id => 2, :nickname => 'nickname_2', :text => "test_nickname2_#{index}") ruby-1.9.2-p290 :012?> sleep(1) ruby-1.9.2-p290 :013?> ActivityFeed.aggregate_item(another_item, 1) ruby-1.9.2-p290 :014?> end => 1 ruby-1.9.2-p290 :015 > ruby-1.9.2-p290 :016 > feed = ActivityFeed::Feed.new(1) => #>, @feederboard_aggregate=#>> ruby-1.9.2-p290 :017 > pp feed.page(1, true) [#, #, #, #, #, #, #, #, #, #] => [#, #, #, #, #, #, #, #, #, #] ruby-1.9.2-p290 :018 > ``` ### Updating and Removing Activity Feed Items You can use the following methods to update and removing activity feed items, respectively: ```ruby ActivityFeed.update_item(user_id, item_id, timestamp, aggregate = false) ActivityFeed.delete_item(user_id, item_id, aggregate = false) ``` ## Future Plans * Suggestions? ## Contributing to Activity Feed * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it * Fork the project * Start a feature/bugfix branch * Commit and push until you are happy with your contribution * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. * 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. ## Copyright Copyright (c) 2011-2012 David Czarnecki. See LICENSE.txt for further details.