README.markdown in activity_feed-1.4.0 vs README.markdown in activity_feed-2.0.0.rc1

- old
+ new

@@ -1,12 +1,13 @@ # ActivityFeed -Activity feeds backed by Redis +Activity feeds backed by Redis. Activity feeds may also be referred to as timelines or +news feeds. ## Compatibility -The gem has been built and tested under Ruby 1.9.2-p290 and Ruby 1.9.3-p0 +The gem has been built and tested under Ruby 1.8.7 and Ruby 1.9.3. ## Installation `gem install activity_feed` @@ -17,248 +18,286 @@ 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 +### Basic configuration options + ```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' +require 'activity_feed' + +ActivityFeed.configure do |configuration| + configuration.redis = Redis.new(:host => '127.0.0.1', :port => 6379) + configuration.namespace = 'activity_feed' + configuration.aggregate = false + configuration.aggregate_key = 'aggregate' + configuration.page_size = 25 +end ``` -## Usage +* `redis`: The Redis connection instance to be used. +* `namespace`: Namespace to isolate ActivityFeed data in Redis. +* `aggregate`: Determines whether or not, by default, various calls will pull from the aggregate activity feed for a user. +* `aggregate_key`: Further isolates the aggregate ActivityFeed data. +* `page_size`: Number of activity feed items to be retrieved per-page. -Make sure to set the Redis connection for use by the ActivityFeed classes. +### Advanced configuration options -```ruby -$redis = Redis.new(:host => '127.0.0.1', :port => 6379) -ActivityFeed.redis = $redis -``` +* `item_loader`: ActivityFeed supports loading items from your ORM (e.g. ActiveRecord) or your ODM (e.g. Mongoid) when a page for a user's activity feed is requested. This option should be set to a Proc that will be called passing the item ID as its only argument. -### Memory-backed persistence +For example: -ActivityFeed defaults to using memory-backed persistence, storing the full item as JSON in Redis. +Assume you have defined a class for storing your activity feed items in Mongoid as follows: ```ruby -require 'redis' -$redis = Redis.new(:host => 'localhost', :port => 6379) -require 'activity_feed' -ActivityFeed.redis = $redis -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') -feed = ActivityFeed::Feed.new(1) -feed.page(1) -``` +require 'mongoid' -### ActiveRecord persistence +module ActivityFeed + module Mongoid + class Item + include ::Mongoid::Document + include ::Mongoid::Timestamps -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: + field :user_id, type: String + validates_presence_of :user_id -```ruby -ActivityFeed.persistence = :active_record -``` + field :nickname, type: String + field :type, type: String + field :title, type: String + field :text, type: String + field :url, type: String + field :icon, type: String + field :sticky, type: Boolean -Example: + index :user_id -```ruby -require 'active_record' + after_save :update_item_in_activity_feed -ActiveRecord::Base.establish_connection( - :adapter => "sqlite3", - :database => ":memory:" -) + private -ActiveRecord::Migration.verbose = false - -ActiveRecord::Schema.define do - create_table :activity_feed_items, :force => true do |t| - t.integer :user_id - t.string :nickname - t.string :type - t.string :title - t.text :text - t.string :url - t.string :icon - t.boolean :sticky - - t.timestamps + def update_item_in_activity_feed + ActivityFeed.update_item(self.user_id, self.id, self.updated_at.to_i) + end + end end - - add_index :activity_feed_items, :user_id end - -require 'redis' -$redis = Redis.new(:host => 'localhost', :port => 6379) -require 'activity_feed' -ActivityFeed.redis = $redis -ActivityFeed.persistence = :active_record -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') -feed = ActivityFeed::Feed.new(1) -feed.page(1) ``` -### MongoMapper persistence +You would add the following option where you are configuring ActivityFeed as follows: -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 +ActivityFeed.item_loader = Proc.new { |id| ActivityFeed::Mongoid::Item.find(id) } ``` -Make sure MongoMapper is configured correctly before setting this option. -If using Activity Feed outside of Rails, you can do: +If you need to handle any exceptions when loading activity feed items, please do this in the Proc. -```ruby -MongoMapper.connection = Mongo::Connection.new('localhost', 27017) -MongoMapper.database = 'activity_feeds_production' -``` +## Usage -```ruby -require 'mongo_mapper' -MongoMapper.connection = Mongo::Connection.new('localhost', 27017) -MongoMapper.database = 'activity_feed_gem_test' -require 'redis' -$redis = Redis.new(:host => 'localhost', :port => 6379) -require 'activity_feed' -ActivityFeed.redis = $redis -ActivityFeed.persistence = :mongo_mapper -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') -feed = ActivityFeed::Feed.new(1) -feed.page(1) -``` +### Developing an Activity Feed for an Individual -### Mongoid persistence +Below is a complete example using Mongoid as our persistent storage for activity feed items. +The example uses callbacks to update and remove items from the activity feed. As this example +uses the `updated_at` time of the item, updated items will "bubble up" to the top of the +activity feed. -ActivityFeed can also use Mongoid 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 = :mongoid -``` +# Configure Mongoid +require 'mongoid' -Make sure Mongoid is configured correctly before setting this option. -If using Activity Feed outside of Rails, you can do: - -```ruby Mongoid.configure do |config| config.master = Mongo::Connection.new.db("activity_feed_gem_test") end -``` -```ruby -require 'mongoid' -Mongoid.configure do |config| - config.master = Mongo::Connection.new.db("activity_feed_gem_test") -end -require 'redis' -$redis = Redis.new(:host => 'localhost', :port => 6379) -require 'activity_feed' -ActivityFeed.redis = $redis -ActivityFeed.persistence = :mongoid -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') -feed = ActivityFeed::Feed.new(1) -feed.page(1) -``` +# Create a class for activity feed items +module ActivityFeed + module Mongoid + class Item + include ::Mongoid::Document + include ::Mongoid::Timestamps -### Ohm persistence + field :user_id, type: String + validates_presence_of :user_id -ActivityFeed can also use Ohm to persist the items in Redis. You can set this using: + field :nickname, type: String + field :type, type: String + field :title, type: String + field :text, type: String + field :url, type: String + field :icon, type: String + field :sticky, type: Boolean -```ruby -require 'redis' -$redis = Redis.new(:host => 'localhost', :port => 6379) -require 'activity_feed' -ActivityFeed.redis = $redis -ActivityFeed.persistence = :ohm -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'Text') -ActivityFeed.create_item(:user_id => 1, :nickname => 'David Czarnecki', :type => 'activity-type', :text => 'More text') -feed = ActivityFeed::Feed.new(1) -feed.page(1) -``` + index :user_id -### Custom persistence + after_save :update_item_in_activity_feed + after_destroy :remove_item_from_activity_feed -ActivityFeed can also use a custom class to do more customization. You can set this using: + private -```ruby -ActivityFeed.persistence = :custom -``` + def update_item_in_activity_feed + ActivityFeed.update_item(self.user_id, self.id, self.updated_at.to_i) + end -This will try to load the following class: + def remove_item_from_activity_feed + ActivityFeed.remove_item(self.user_id, self.id) + end + end + end +end -```ruby -ActivityFeed::Custom::Item -``` +# Configure ActivityFeed +require 'activity_feed' -If you set persistence to be `:foo`, it would try to load the following class: +ActivityFeed.configure do |configuration| + configuration.redis = Redis.new(:host => '127.0.0.1', :port => 6379) + configuration.namespace = 'activity_feed' + configuration.aggregate = false + configuration.aggregate_key = 'aggregate' + configuration.page_size = 25 + configuration.item_loader = Proc.new { |id| ActivityFeed::Mongoid::Item.find(id) } +end -```ruby -ActivityFeed::Foo::Item -``` +# Create a couple of activity feed items +activity_item_1 = ActivityFeed::Mongoid::Item.create( + :user_id => 'david', + :nickname => 'David Czarnecki', + :type => 'some_activity', + :title => 'Great activity', + :text => 'This is text for the activity feed item', + :url => 'http://url.com' +) -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. +activity_item_2 = ActivityFeed::Mongoid::Item.create( + :user_id => 'david', + :nickname => 'David Czarnecki', + :type => 'some_activity', + :title => 'Another great activity', + :text => 'This is some other text for the activity feed item', + :url => 'http://url.com' +) -### Feeds and Aggregation Feeds +# Pull up the activity feed +feed = ActivityFeed.feed('david', 1) + => [#<ActivityFeed::Mongoid::Item _id: 4fe0ce26421aa91fc2000004, _type: nil, created_at: 2012-06-19 19:08:22 UTC, updated_at: 2012-06-19 19:08:22 UTC, user_id: "david", nickname: "David Czarnecki", type: "some_activity", title: "Another great activity", text: "This is some other text for the activity feed item", url: "http://url.com", icon: nil, sticky: nil>, #<ActivityFeed::Mongoid::Item _id: 4fe0ce26421aa91fc2000003, _type: nil, created_at: 2012-06-19 19:08:22 UTC, updated_at: 2012-06-19 19:08:22 UTC, user_id: "david", nickname: "David Czarnecki", type: "some_activity", title: "Great activity", text: "This is text for the activity feed item", url: "http://url.com", icon: nil, sticky: nil>] -You can access an activity feed in a couple of ways. +# Update an actitivity feed item +activity_item_1.text = 'Updated some text for the activity feed item' +activity_item_1.save -```ruby -ActivityFeed.feed(user_id) # return an instance of ActivityFeed::Feed +# Pull up the activity feed item and notice that the item you updated has "bubbled up" to the top of the feed +feed = ActivityFeed.feed('david', 1) + => [#<ActivityFeed::Mongoid::Item _id: 4fe0ce26421aa91fc2000003, _type: nil, created_at: 2012-06-19 19:08:22 UTC, updated_at: 2012-06-19 19:11:27 UTC, user_id: "david", nickname: "David Czarnecki", type: "some_activity", title: "Great activity", text: "Updated some text for the activity feed item", url: "http://url.com", icon: nil, sticky: nil>, #<ActivityFeed::Mongoid::Item _id: 4fe0ce26421aa91fc2000004, _type: nil, created_at: 2012-06-19 19:08:22 UTC, updated_at: 2012-06-19 19:08:22 UTC, user_id: "david", nickname: "David Czarnecki", type: "some_activity", title: "Another great activity", text: "This is some other text for the activity feed item", url: "http://url.com", icon: nil, sticky: nil>] ``` -or +### Developing an Aggregate Activity Feed for an Individual ```ruby -ActivityFeed::Feed.new(user_id) -``` +# Configure Mongoid +require 'mongoid' -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`. +Mongoid.configure do |config| + config.master = Mongo::Connection.new.db("activity_feed_gem_test") +end -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. +# Create a class for activity feed items +module ActivityFeed + module Mongoid + class Item + include ::Mongoid::Document + include ::Mongoid::Timestamps -Below is an example of an aggregate feed: + field :user_id, type: String + validates_presence_of :user_id -```ruby + field :text, type: String + + after_save :update_item_in_activity_feed + after_destroy :remove_item_from_activity_feed + + private + + def update_item_in_activity_feed + ActivityFeed.update_item(self.user_id, self.id, self.updated_at.to_i) + end + + def remove_item_from_activity_feed + ActivityFeed.remove_item(self.user_id, self.id) + end + end + end +end + +# Configure ActivityFeed require 'activity_feed' -require 'pp' -$redis = Redis.new(:host => '127.0.0.1', :port => 6379) -ActivityFeed.redis = $redis -ActivityFeed.persistence = :ohm +ActivityFeed.configure do |configuration| + configuration.redis = Redis.new(:host => '127.0.0.1', :port => 6379) + configuration.namespace = 'activity_feed' + configuration.aggregate = true + configuration.aggregate_key = 'aggregate' + configuration.page_size = 25 + configuration.item_loader = Proc.new { |id| ActivityFeed::Mongoid::Item.find(id) } +end + +# Create activity feed items for a couple of users and aggregate the activity feed items from the second user in the first user's activity feed 1.upto(5) do |index| - item = ActivityFeed.create_item(:user_id => 1, :nickname => 'nickname_1', :text => "text_#{index}") - sleep(1) - another_item = ActivityFeed.create_item(:user_id => 2, :nickname => 'nickname_2', :text => "test_nickname2_#{index}") - sleep(1) - ActivityFeed.aggregate_item(another_item, 1) + ActivityFeed::Mongoid::Item.create( + :user_id => 'david', + :text => "This is from david's activity feed" + ) + + sleep(1) # Sleep a little so we make sure to have unique timestamps between activity feed items + + another_item = ActivityFeed::Mongoid::Item.create( + :user_id => 'unknown', + :text => "This is from unknown's activity feed" + ) + + sleep(1) + + ActivityFeed.aggregate_item('david', another_item.id, another_item.updated_at.to_i) end -feed = ActivityFeed::Feed.new(1) -pp feed.page(1, true) +# Pull up the aggregate activity feed +pp feed = ActivityFeed.feed('david', 1, true) + [#<ActivityFeed::Mongoid::Item _id: 4fe289248bb895b79500000a, _type: nil, created_at: 2012-06-21 02:38:28 UTC, updated_at: 2012-06-21 02:38:28 UTC, user_id: "unknown", text: "This is from unknown's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe289238bb895b795000009, _type: nil, created_at: 2012-06-21 02:38:27 UTC, updated_at: 2012-06-21 02:38:27 UTC, user_id: "david", text: "This is from david's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe289228bb895b795000008, _type: nil, created_at: 2012-06-21 02:38:26 UTC, updated_at: 2012-06-21 02:38:26 UTC, user_id: "unknown", text: "This is from unknown's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe289218bb895b795000007, _type: nil, created_at: 2012-06-21 02:38:25 UTC, updated_at: 2012-06-21 02:38:25 UTC, user_id: "david", text: "This is from david's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe289208bb895b795000006, _type: nil, created_at: 2012-06-21 02:38:24 UTC, updated_at: 2012-06-21 02:38:24 UTC, user_id: "unknown", text: "This is from unknown's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe2891f8bb895b795000005, _type: nil, created_at: 2012-06-21 02:38:23 UTC, updated_at: 2012-06-21 02:38:23 UTC, user_id: "david", text: "This is from david's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe2891e8bb895b795000004, _type: nil, created_at: 2012-06-21 02:38:22 UTC, updated_at: 2012-06-21 02:38:22 UTC, user_id: "unknown", text: "This is from unknown's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe2891d8bb895b795000003, _type: nil, created_at: 2012-06-21 02:38:21 UTC, updated_at: 2012-06-21 02:38:21 UTC, user_id: "david", text: "This is from david's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe2891c8bb895b795000002, _type: nil, created_at: 2012-06-21 02:38:20 UTC, updated_at: 2012-06-21 02:38:20 UTC, user_id: "unknown", text: "This is from unknown's activity feed">, + #<ActivityFeed::Mongoid::Item _id: 4fe2891b8bb895b795000001, _type: nil, created_at: 2012-06-21 02:38:19 UTC, updated_at: 2012-06-21 02:38:19 UTC, user_id: "david", text: "This is from david's activity feed">] ``` -### Updating and Removing Activity Feed Items +## ActivityFeed Caveats -You can use the following methods to update and removing activity feed items, respectively: +`ActivityFeed.remove_item` can ONLY remove items from a single user's activity feed. If you allow activity feed +items to be deleted from a user's activity feed, you will need to propagate that delete out to all the other +feeds in which that activity feed item may have been aggregated. +## ActivityFeed method summary + ```ruby -ActivityFeed.update_item(user_id, item_id, timestamp, aggregate = false) -ActivityFeed.delete_item(user_id, item_id, aggregate = false) +# Item-related + +ActivityFeed.update_item(user_id, item_id, timestamp, aggregate = ActivityFeed.aggregate) +ActivityFeed.aggregate_item(user_id, item_id, timestamp) +ActivityFeed.remove_item(user_id, item_id) + +# Feed-related + +ActivityFeed.feed(user_id, page, aggregate = ActivityFeed.aggregate) +ActivityFeed.feed_between_timestamps(user_id, starting_timestamp, ending_timestamp, aggregate = ActivityFeed.aggregate) +ActivityFeed.total_pages_in_feed(user_id, aggregate = ActivityFeed.aggregate, page_size = ActivityFeed.page_size) +ActivityFeed.total_items_in_feed(user_id, aggregate = ActivityFeed.aggregate) +ActivityFeed.trim_feed(user_id, starting_timestamp, ending_timestamp, aggregate = ActivityFeed.aggregate) +ActivityFeed.remove_feeds(user_id) ``` -## Contributing to Activity Feed +## Contributing to ActivityFeed * 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 @@ -266,7 +305,6 @@ * 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. - +Copyright (c) 2011-2012 David Czarnecki. See LICENSE.txt for further details. \ No newline at end of file