0.0
No commit activity in last 3 years
No release in over 3 years
A flexible feed system, allowing for everything from automated events to nested user-generated discussions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

FlexibleFeeds Gem Version Build Status Code Climate

FlexibleFeeds allows to you to create dynamic feed systems for a Rails app. As a comparison, check out the Public Activity we find here on GitHub. It lists a variety of events, from creating branches to pushing new versions. With FlexibleFeeds, you can get similar functionality, but with some added perks, such as voting, sorting, and nesting events. This allows FlexibleFeeds to smoothly aggregate system-generated notices with user-generated posts and comments.

Compatibility

FlexibleFeeds is a Rails Engine, which means it plays best with Rails applications. So far, it's only been tested with Rails 4.

Installation

  1. Add FlexibleFeeds to your gemfile: gem 'flexible_feeds'
  2. Run bundler: bundle install
  3. Run this in your app folder: rake flexible_feeds:install:migrations
  4. Run your migrations: rake db:migrate
  5. Add flexible_feeds to the models you want to have feeds
  6. Add acts_as_eventable to the models you want to appear in feeds
  7. Add acts_as_moderator to the models you want to be moderators
  8. Add acts_as_follower to the models you want to be followers
  9. Add acts_as_voter to the models you want to cast votes

Creating Feeds

Scenario: We want our User model to have a public activity feed, similar to the one on GitHub.

First, add flexible_feeds to the User model that'll contain the feed. By default, flexible_feeds will create a has_one relationship with its feed. This feed will be created automatically for the User in an after_create callback. You can then access the feed with a command like @user.feed

If you want the User to have multiple feeds, you can do so with:

flexible_feeds has_many: true

Note that FlexibleFeeds will not automatically create a feed in this case. You must manually create all of them on your own, like so:

@user.feeds.create(name: "Public Activity")
@user.feeds.create(name: "Private Activity")

You can then look-up your feeds by name, such as @user.feed_named("Public Activity").

Creating Events

Scenario: When a user leaves a group, we want an event to appear in both her feed and the group's feed.

First, create an event model. Let's call it MembershipTermination. Within MembershipTermination, include acts_as_eventable. The model might look something like this:

class MembershipTermination < ActiveRecord::Base belongs_to :user belongs_to :group acts_as_eventable add_to_feeds: :custom_feeds

def custom_feeds
  [user.feed, group.feed]
end

end

Then when the user terminates her account, the event will automatically appear in both her and the group's feed:

MembershipTermination(user: @user, group: @group)
@user.feed.events # => returns a list of events, including the termination

acts_as_eventable take an add_to_feeds arguments, which in turn accepts a pointer to a method, which should return an array of feeds you want to post the event to after_save.

You can also add an event to feeds manually:

@membership_termination.post_to_feeds(@another_group.feed, @admin.feed)

Sorting Events

Scenario: You want to sort events by their popularity.

Events have nearly a dozen scopes intended for ordering. To get a descending list of events by popularity, you simply have to call:

@feed.events.popular

The scopes include:

:newest # the most recently _updated_ events
:oldest # the oldest events
:loudest # the events with the most children (refer to Creating Nested Events)
:quietest # the events with the fewest children
:controversial # the events with the most conflicting votes
:uncontroversial # the events with the most vote agreement
:simple_popular # the events with the highest rating
:simple_unpopular # the events with the lowest rating
:popular # the events with the highest lower-bound rating
:unpopular # the events with the lowest lower-bound rating

(For an excellent description of how popular and unpopular work--and why you should use them instead of simple_popular and simple_unpopular--refer to this blog post by Evan Miller.)

Displaying Events

Scenario: You want to display the ten newest events for a user.

First, grab the events:

@events = @user.feed.events.newest.limit(10).includes(:eventable)

Then cycle through them in your view, displaying the associated eventable:

<%= @events.each do |event| %>
  <%= event.eventable %>
<% end %>

Then create a partial for the eventable:

# /views/membership_terminations/_membership_termination.html.erb
<p>User <%= membership_termination.user.name %> left group <%= membership_termination.group.name %>.</p>

Voting On Events

Scenario: You want your users to be able to vote upon events.

Events have a cast_vote method which you can add to your controller. Just pass in the voter and the value of the vote. (The value can be -1 or 1.)

@event.cast_vote({ voter: current_user, value: params[:value] })

Any model can vote, so both of these lines could work too:

@event.cast_vote({ voter: @group, value: 1 })
@event.cast_vote({ voter: @bot, value: -1 })

If a voter has already voted upon an event, FlexibleFeeds will check if the pre-existing vote has the same value. If it does, it will delete both the new and old vote. If they have opposite values, then it will replace the old value with the new one.

Nesting Events

Scenario: You want an event to be commentable.

First, pass is_parent to the event's acts_as_eventable. By default, is_parent will accept any eventable model as a child. If you want to be more specific, you can pass in a hash of permitted (or unpermitted) children.

# accepts every eventable model that is_child
acts_as_eventable is_parent: true

# accepts only Comment and Reference, assuming they're is_child
acts_as_eventable is_parent: { permitted_children: [Comment, Reference] }

# accepts all is_child events, except Post
acts_as_eventable is_parent: { unpermitted_children: [Post] }

Next, add is_child to child events' acts_as_eventable:

acts_as_eventable is_child: true

After that, you can add a child to a parent like so:

@post = Post.find(params[:id])
@comment = Comment.create(comment_params)
@comment.child_of(@post.event)

You can also do the opposite:

@post = Post.find(params[:id])
@comment = Comment.create(comment_params)
@post.parent_of(@comment.event)

If you made your Comment model both is_parent and is_child, you could get threaded comments like so:

@parent_comment = Comment.find(params[:id])
@child_comment = Comment.create(comment_params)
@parent_comment.parent_of(@child_comment.event)

You can access children in two ways. If you only want the immediate children:

@post.children

If you want the immediate children as well as their children (and their children, and so on), call:

@post.descendants

This is ideal for gathering all comments in a deeply nested thread in a single call to the database. Note that descendants only works for top-level parents. Intermediate level parents (such as a nested comment), cannot use it.

If you should need to, you can also query up the family tree for the immediate parent:

@comment.parent

Or if you want the top-level parent, you can do so with:

@comment.ancestor

Following

Scenario: You want users to be able to follow specific feeds, which will be aggregated in their homepage.

First, place acts_as_follower in your User model:

class User < ActiveRecord::Base
  acts_as_follower
end

Then set the user to follow the desired feed:

@user.follow(@feed)

Later, the user can unfollow the feed:

@user.unfollow(@feed)

You can also check to see if the user is a follower:

@user.is_following?(@feed)

And you can also get an array of the feeds the user is follow:

@user.followed_feeds

Finally, you can an aggregated array of all the events on the followed feeds:

@user.aggregate_follows

You can also query in the opposite direction:

@feed.followers
@feed.followers_include?(@user)
@feed.add_follower(@user)
@feed.remove_follower(@user)

Issues

FlexibleFeeds is still very young, and both its functionality and its documentation are bound to be lacking. If you're having trouble with something, feel free to open an issue.

Contributing

Feel free to send us a pull request. Public methods, callbacks, and validations should have Rspec tests to back them up.

Contributors

Timothy Baron

License

Released under the MIT license.