#406 Public Activity
Feb 13, 2013 | 10 minutes | Active Record, Plugins
Learn how to easily add a user activity feed using the public_activity gem. Here I show both the default setup using model callbacks and a manual way to trigger activities.
- Download:
- source code
- mp4
- m4v
- webm
- ogv



Great episode thank you but I have a question about listing activities. Why are some activities duplicated like update?
If you edit the object multiple times, then multiple Activities are created each time.
Public_activity looks like a great gem but we had some trouble with this approach in production. When tracking a number of changes on a number of models it bumped writes to the database quite substantially. Then when drawing back the associated objects on multiple activities there's a performance hit again.
We build our own solution for this called Redcrumbs.
It works in a very similar way but uses Redis as a backend. The benefits include superfast reads and writes but also Redis has an option to simply expire old keys, similar to memcached.
It also allows common attributes to be cached on the activity object, so that in 90% of cases the associated object doesn't have to be drawn from the primary database.
We've been using it in production to provide news feeds in our games for at least 6 months without a hitch.
Public_activity looks great for smaller sites though and there's an argument for keeping everything in the main database and on the same database structure.
You make a good point about performance and storage. Redis is a good idea as backend - which I will look into.
No sweat, let me know if I can help!
Yeah redis is incredibly fast and fits pretty well in storing non critical data like user activities. I have already read some of the documentation and played a bit with it in development but haven't yet used it in a production environment. Could you recommend some resources where i can learn more about Redis? Which path did you follow?
Would you like to discuss this further on Github Issues? This is really interesting, but querying for Activities with Redis requires a whole different process.
I love the idea of using redis as the backend too!
I was just about to ask Ryan if he could do a cast on how to achieve this from scratch... with something like Redis :)
Could you write a blog post about implementing a simple activity tracker from scratch Jon? Interested to see how you approach it.
+1 This is a good idea :) Thanks for your sharing.
Yep, using activerecord/sql-db will definitely be an issue. I wonder whether Ryan is planning on doing a "from scratch" implementation using redis. I will check out your gem.
Redcrumbs looks good... I tried it on one of my app and while overall it works good, BUT It relies on callback methods.. So lets say I have a post (created by User B) which a moderator (User A) edits... The creator of that crumb would still be the original post owner(User B) and not the moderator(User A)...
How to make it reference the moderator as the creator of the crumb/change since he was the one to edit the post?
As Ryan rightly pointed out, using callbacks becomes messy...
Used similar gem for my app called 'timeline_fu'. In rails 3.2 found working only this one - github.com/crftr/timeline_fu.git
Looks like the gem provides a lot of features out of the box but I think for any serious social network app, activity feeds are quite important so my personal preference is to build this something from scratch and that is what we have done in MeroCampus.com
But for simpler app it is the way to go. Thanks.
How does this handle fetching the associated models? This feels like an n+1 query to me, no?
All associations are polymorphic, so it is a hard topic. I personally cache whole pages of activities.
It's at least going to involve a select on every corresponding table included in the activities, there's really no way around that.
That's why I suggest saving an arbitrary hash of attributes on the activity object itself. 9 times out of 10 you only want, say, the ID and Name of the associated object in order to generate a link to it.
Exactly, that is the main reason
paramsexists, to cache important attributes of associated objects. See http://rubydoc.info/gems/public_activity/PublicActivity/Tracked/ClassMethods#tracked-instance_methodIs there a way to track views? e.g., steve viewed ryan's lasagne recipe.
Create a new public activity in the show action?
Interesting timing, as I've been working on a couple different approaches for activity feeds this month. My current process goes like this:
By doing the latter, there are no lookups required for the trackable objects when generating your activity feed. Its super fast. Activity streams can be generated purely with the denormalized data, but we have the trackable references in case we need them. Also, you don't lose the data (post name, etc) when an object is deleted, and can instead defer handling of clickthroughs to deleted objects.
That's exactly what public_activity does, minus delaying. Also using delayed_job makes no sense, because it still has writes to the db, so you might as well write the activity. Better option is something redis based.
Interesting, as from watching this video it looked like the trackable objects had to be loaded.
The reason to use DJ is if you're writing the activity from a model/observer callback, to defer a few additional SQL calls required to populate the denormalized data. If you have everything you need at the time of writing, its unnecessary.
Ah yes, that makes sense.
+1 on that refactoring at 9:00, seems essential. I cringed seeing "controller" in that model, too!
+1
Yes, this is ugly, but completely optional. You can do creation manually using #create_activity and only use #tracked to set defaults, if for example you know you will always need to cache recipe's name.
Is there a way to set a column in the Activities table to attr_accessible? (no model?) I've added a custom field to query against but need it to be accessible? - Thanks!
Hi @iashraf. I'll prepare a wiki page for this and post the link here :)
In some cases, you can help the performance by pushing the assets to a different database. We have to keep detailed logs on a web application to track back changes made and can do this through having a beefier database server to handle the queries of the live logs.
Then in your special model:
To keep those pesky credentials from being in your application code.
If you want to reuse this connection in multiple models, you should create a new abstract class and inherit from it, because connections are tightly coupled to classes (as explained here, here, and here), and new connections will be created for each class.
If that is the case, set things up like so:
I had to so something like that too in my own app.
The simplest solution was to unload all the publishing of activities into a background job and cache the view of all activities. App response is around 150-200ms so I am happy. Activities are generally published within minutes so that is good enough.
I don't really like when a solution/gem outgrows the problem in complexity, plus callbacks are evil.
PS: I don't want to know how complex this would get if you also want comments on an activity to be published on the 'activity item'. So comment on 'A commented on picture of B' -> creates comment on picture of B, which is what I had to do in my app.
Good idea about delaying the activity creation!
I as an admin have 1200 friends. Delaying the activity creation is the only real option even if it only takes a few seconds.
+1
Hi - Does anyone know how to group the output of the activity list by date in the view? So it looks something like:
Thursday, February 14, 2013 - 13:40: Sample activity - 13:30: Sample activity - 13:20: Sample activity Tuesday, February 12, 2013 - 15:40: Sample activity - 14:30: Sample activityThanks!
There's even a Railscast about it, though quite old, but still applies http://railscasts.com/episodes/29-group-by-month
Thanks Ryan for a great (as usual) screencast, and Piotrek for a nice looking gem.
My main problem with the polymorphic approach is that we loose information when models get destroyed. For instance, using the example in the screencast, I'd like to display the name of a recipe that got deleted.
This obviously means that the activity has to duplicate all the data it wants be able to display after model destruction.
Do you know of any work-around or non-polymorphic gem that does that?
Why not just mark the model as 'deleted' instead of actually destroying it?
+1
I really love this gem, It works just perfect for my current project(still in development) except sooner or later I am going to have to replace it with alternative or write from scratch as I hit the performance issue:(
Hi!
Can you tell what performance issues you have?
I've been using Streama with Mongoid, which has a nice DSL for logging activity stream and specify cached attributes. https://github.com/christospappas/streama The only thing missing is auto expire old activities.
Might be a basic Rails question but:
In
@activities = PublicActivity::Activity.order("created_at desc").where(owner_id: current_user.friend_ids, owner_type: "User")where did
current_user.friend_idscome from?friend_id is an attribute of Friendship, not User, right? And you can just pluralize an attribute to get an array?
friend_id comes from the sample "Recipe Application" which has "friends".
Ryan, what do you think of audited gem - https://github.com/collectiveidea/audited. This seems to be another solution to provide similar functional.
This is more of a papertrail, not activity tracking.
Yes, but still...
Does anyone know how to group similar activities and shows as one? at the same time that others activities shows normally, Just like these:
Jhon removed a post
Jhon updated Post 1
Jhon created 3 posts:
-Post 1
-Post 2
-Post 3
PD: Thanks for the gem, so useful!
Is it possible to import past activities? My app already has a lot of data generated by the User. Curious if there is a migration or some sort of import that exists with this for implementing it into an already existing app with a lot of data.
Did I miss something or is there a reason why you can't simply use the model's user for the owner?
In the comment model isn't the comment.user association the owner? Does the gem allow you to simply say
or the like? Seems like accessing the controller is over-kill in most cases.
It doesn't appear that
include PublicActivity::Commonworks in the current release of the gem, so not sure how this appeared to work in the episode without using a pre-release version of the gem.See https://github.com/pokonski/public_activity/issues/62 for workarounds.
Is there any way to only call activities which have an owner in the controller as opposed to the view?
is there a way to use this with the acts_as_commentable gem?
I'm creating a CRM app where I'd like to have a dashboard that displays a feed of customer notes that were added and when. Looks like this gem will be helpful. I can see it being a lot of hits to the db if activity is heavy but I think it's worth a try.
Thanks for another great Railscast!
First sign in through GitHub to post a comment.