A Social Network in Rails: Activity Feed

This post is part of an ongoing feature about creating a social network in Rails. You can see part 1 here, about creating elegant permalinks for your content.

Today I'm presenting a wonderful gem called public_activity , which lets you create activity feeds with a few lines of code:
To begin with, add the gem to your Gemfile, as usual:
gem "public_activity"

Set up the DB table for the activities (assuming you'll use ActiveRecord for it, if you want Mongoid or MongoMapper check the readme):
rails g public_activity:migration
rake db:migrate

We'll use a similar data model as the permalinks post for this guide, where user has_many photos and, now, photos has_one details. We want to show on our feed each time a user creates or updates a photo, and each time a user checks a photo's details. This way, we'll show 2 different ways of tracking activities:

1. Via model callbacks
We set up PublicActivity on our Photo model to track every creation and update, and the user (found via the has_many relationship) as the owner:
class Photo < ActiveRecord::Base
  include PublicActivity::Model

  tracked only: [:create, update], owner: :user
  ...
end

2. Manually in the controller
We want to track and show on the feed when a user checks the details of a photo. For this, we add the Common module to the Details model, since we don't need the tracked method this time:
# in app/models/details.rb
class Details < ActiveRecord::Base
  include PublicActivity::Common
  ...
end
And then we create the activity in the controller, just before we render the photo's details. This time we use the current_user helper to get the user that read the photo's details:
# in app/controllers/details_controller.rb
def show
  @details = Details.find(params[:id])
  @details.create_activity :read, owner: current_user
end

Now, let's list those activities on a nice feed! To get all the activities related to a user, we grab the ones which he owns plus the ones related to his photos (through the details):
# in config/routes.rb
resources :activities, only: :index

# in app/controllers/activities_controller.rb
class ActivitiesController < ApplicationController
  def index
    details_ids = User.photos.map { |photo| photo.details.id }

    activities_by_owner = PublicActivity::Activity.where(owner: current_user)
    activities_by_reads = PublicActivity::Activity.where("trackable_type = 'Details' AND trackable_id in ?", details_ids)

    @activities = activities_by_owner.or(activities_by_reads).distinct
  end
end

# in app/views/activities/index.html.erb
<% @activities.each do |activity| %>
    <%= activity.owner.name if activity.owner %>
      <%= render_activity activity %>
  <% end %>
<% end %>
(To add that nifty or method to your app, use this monkey patch) Lastly, let's create a partial for each kind of activity, so they can be tailored to its content. Mind the file names!
# in app/views/public_activity/photo/_create.html.erb
<% if photo = activity.trackable %>
  added <%= link_to photo.name, photo_path(photo) %>
<% else %>
  added a photo that does not exist anymore.
<% end %>

# in app/views/public_activity/comment/_read.html.erb
<% if comment = activity.trackable %>
  read a comment on <%= link_to comment.photo.name, photo_path(comment.photo) %>
<% else %>
  read a comment on a photo that does not exist anymore.
<% end %>
And that's it! One more step towards our Rails-powered social network :D

PD: There's also a no-gem version of this by jules2689, check it out!

No comments:

Post a Comment