Plugin for implementing social network walls
Ruby
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
app/models
lib
test
MIT-LICENSE
README.rdoc
Rakefile
init.rb
install.rb
uninstall.rb

README.rdoc

ActsAsWall

Plugin that allows implementing Facebook/Twitter style walls, where events from multiple walls are aggregated. Requires Rails 3.

Models

Suppose an application where users have walls you can post on. There are also groups the users can join, and they also have an associated wall. While a user belongs to a group, he will be subscribed to the events of that wall. The implementation using acts_as_wall is as follows:

class Group < ActiveRecord::Base
  acts_as_wall  # This model has an associated wall
  acts_as_event # This model will appear in events (e.g. a group was created by Bob)
end

class User < ActiveRecord::Base
  acts_as_wall
  acts_as_listener # Users listen to walls
end

class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group

  # We want that a user listens to a group's wall whenever the user belongs to the group.
  keeps_listener :user=>:group
end

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :user_target, :class_name => 'User'

  # A user listens to another user's wall if they're friends.
  keeps_listener :user=>:user_target
end

To get all the events from walls that a user is subscribed to (usually employed in a home page):

user.feed.events # Returns events from the walls a user is subscribed

To get all the events from a wall:

group.wall.events # Returns events from a group's wall
user.wall.events  # Returns events from a user's wall

Also, in some cases you will want that a user is subscribed to its own wall. Remember to create a self-listener for that purpose:

class User < ActiveRecord::Base
  acts_as_wall
  acts_as_listener
  after_create :add_self_listener

  protected
  def add_self_listener
    listen_to self.wall
  end
end

This is done automatically by using:

acts_as_listener :self => true

Controllers

There is a simple way to fire events. First, you need to add WallResponder to your controller responders. An easy way to achieve this using the responders gem is:

class ApplicationController < ActionController::Base
  responders :wall
end

Then, for each controller, you can use the announce method to declare that an event must be logged and announced in a wall. It is important to note that you need to use respond_with(@resource) for everything to work:

class GroupsController < ActionController::Base
  announce :current_user, :user, :except=>:destroy
end

This will access controller.current_user and @group.user to get the walls to add the event to. It will process any non-get action and announce the event in these walls.

You can call announce more than once in case there's not a single rule that suits all your cases:

class GroupsController < ActionController::Base
  announce :current_user
  announce :group, :except=>:update
end

Also, procs are supported, and other conditions can be used:

announce :current_user, :if=>lambda{ |post, controller| controller.current_user.friend_of?(post.user) }, :except=>:destroy

Finally, what if you don't want a different object to be included into the event? Sometimes you don't want to include a highly volatile object which is likely to be destroyed, as you would lose its associated event. This happens often with nested resources, or with resources that represent relationships. To solve those cases, you can specify the object that will be stored in the event:

class Membership < ActiveRecord::Base
  belongs_to :group
  belongs_to :user

  # Don't include the membership object, but the group the user belongs to
  acts_as_event :object=>:group
end

Events can be tested by using test helper methods assert_event, assert_no_event, assert_listener and assert_no_listener.

Grouping events

When rendering events, most times you want a timeline with some events grouped. You'd rather show this:

Alice and Bob commented the post "Ruby on Rails"

instead of

Alice commented the post "Ruby on Rails"
Bob commented the post "Ruby on Rails"

How to do this? The plugin makes it easy to group events using the :group_by argument when calling acts_as_event:

class Comment
  acts_as_event :subobject=>:commentable, :group_by=>:commentable
end

This will group all events in which the commentable field is the same. In order for the events to be grouped, it is important to store the commentable field, so it is stored into the event's subobject field thanks to :subobject => :commentable.

When querying for the events, just use the arrange scope to get a list of grouped events:

@user.feed.past.arrange

It works well with will_paginate plugin:

@user.feed.past.paginate(:page=>params[:page], :per_page=>20).arrange

Notifications

You may usually want to notify users instead of posting to their walls. This can be specified at the model:

class User < ActiveRecourd::Base
  acts_as_listener :mailer => true
end

This will make the responder call automatically UserMailer.groups_update(user, event), UserMailer.memberships_create(user, event), etc.

To notify users instead of announcing events on their walls, use notify method instead of announce:

class PostsController < ActionController::Base
  notify :author, :only=>:destroy
end

To get your notifications:

current_user.tray.events

Other controllers

You can fire events from other controllers, while keeping the settings from the original controllers:

class GroupsController < ActionController::Base
  notify :author, :only=>:destroy
  def destroy
    @group = Group.find params[:id]

    posts = @group.posts
    @group.destroy
    fire_event posts, :destroy, :posts

    respond_with @group
  end
end

If defaults don't suit you

You can always use fire_event method if you don't want to use responders:

class GroupsController < ActionController::Base
  notify :members, :only=>:update

  def update
    @group = Group.find params[:id]
    @group.update_attributes(params[:group])
    fire_event @group # equivalent to fire_event @group, :update, :groups
    ...
  end

If none of these methods suit you, you can still manage listeners and events manually in those problematic cases.

For example, you can create listeners:

Listener.create :user=>current_user, :wall=>group.wall
# or
current_user.listen_to group.wall

Or you can create events:

Event.create :object=>group, :controller=>'groups', :action=>'create', :actor=>current_user, :start_at=>DateTime.now

And so on. Enjoy the plugin :)

Copyright © 2010 José Ignacio Fernández (joseignacio.fernandez at gmail.com), released under the MIT license