#407 Activity Feed from Scratch pro
Feb 14, 2013 | 14 minutes |
Views
Creating an activity feed presents some interesting challenges. Here I share my thought process behind my implementation including when to use callbacks and a few tricks with partials.
- Download:
- source codeProject Files in Zip (93.7 KB)
- mp4Full Size H.264 Video (29.3 MB)
- m4vSmaller H.264 Video (15.5 MB)
- webmFull Size VP8 Video (20 MB)
- ogvFull Size Theora Video (34.3 MB)
Ryan, it looks like in the second half of the episode you were really after ActiveRecord's
to_partial_path
, which you can define in Activity model and have it perform all of your path-building logic. It becomes a clear winner when you notice thatbecomes
Makes more sense to have it come from inside the activity object itself. In the view you could then simply call
render activity
, and it would automatically call to_partial_path on the model. When this logic is moved into model like that, presenter is left somewhat empty, and in this specific case becomes excessive.Thanks Maxim
By using a service object you could wrap the save of recipe (e.g.) and the creation of feed in a transaction. Otherwise you could have an activity succeed without its companion feed row being created.
The
track_activity
method is only called if the@recipe.save
succeeds in the episode.Yep, but what if track_activity fails? That's what Steve means I believe.
Ok I understand. I'm not sure I'd want to back out of the transaction because the activity tracking failed though... the recipe saved, the notification failed - seems like a secondary transaction to me, failure of which would be logged, retried and possibly even pushed onto a queue or other out-of-band process.
I don't see why you keep saying that you cannot get the user reference in the model. On create the activity owner would be the creator referenced in the recipe/comment. If other users can update these, you would likely store the last modifier in the database for later reference anyway, so you can also reference them in the callback.
For me, this functionality does belong in a callback and nowhere else, precisely because it is possible that there are other ways, these items can be created. Things like combining/filtering/spam protection would also have to be implemented when you create activities in the controller at some point of professionalization of an app. So this is no argument against callbacks.
Great episode. On quick question though. I would've thought that the presenter in this case would be 'just' a helper. Why did you make it a presenter? Thanks!
Have you thought about doing this with redis as demonstrated by obie here? - http://www.youtube.com/watch?v=dH6VYRMRQFw
Fantastic clip there, thanks for sharing.
+1
I'd love to see a cast on Redis taking activity feeds to the next level Ryan :)
+1 too
thanks for sharing
I implemented an activity feed last year which was a bit more complex than this. It had to store data such as specific actions like updating a models state column (from/to), as well as displaying consecutive activity grouped by the person who did it, and grouping the same consecutive action by the same person as "XX updated XXX (4 times)". Would be nice to see another pro episode that handled this display related stuff, since my code is kind of messy, and it's be cool to see how it might be implemented by someone else.
THX
I am not quite familiar with the concept of Presenters. Can you recommend any articles/videos on the topic?
Railscast #287 covers Presenters.
http://railscasts.com/episodes/287-presenters-from-scratch
This tutorial was very helpful for me! Few weeks ago I've been working on activity feed myself. Any ideas how to aggregate activities to look something like 'Mike followed Ryan and 6 other developers'? Also my trackable object are polymorphic as well. Comment belongs to commentable models, Follow belongs to followable models. So it would be great to show follows and comments based on their polymorphic parent models. Is there any good solution? Thanks!
I'd be interested to hear more about how you're approaching aggregation. I've started implementing an activity stream and am running into this challenge now.
Also, I'm trying to think of a good design pattern for how comments on aggregate stories should work. You can see more here: http://stackoverflow.com/questions/16339228/how-to-approach-aggregating-actvities-in-feeds-and-then-handling-comments
How did you end up solving the aggregation problem?
Displaying activity items generates a lot of sql queries, especially N+1 queries. Is there a way to get around this?
Hi, I know, that this is a bit old question. Anyway for everyone who chalanged this problem, you can use
.includes(:trackable)
, like with any other association.commented on <%= link_to activity.trackable.recipe.name, activity.trackable.recipe %>
For this code, How come I get error undefined method, I'm not using the exact names for my values, I'm doing it this way,
commented on <%= link_to activity.trackable.list.name, activity.trackable %>
but I'm not sure why I don't have to add a
.list
at the end of my code? Instead I can just stop at.trackable
-1 for the Presenters.
Am I missing something, or shouldn't the Receipe and Comment model both have the following included (based on (154 Polymorphic Association episode)?
If not, then I must be missing the point of this polymorphic association.
Thanks!
Why couldn't you do
render "activities/#{activity.trackable_type.underscore}/#{activity.action}", activity.trackable_type.underscore.to_sym => activity
instead of creating the presenter. This seems much easier and less cluttered.
For me the whole presenter thing ended up in NameError, so I ended up using this.
+1
Thanks for this. Much more straight forward. I think you may have a typo at the very end though. This is what worked for me:
render "activities/#{activity.trackable_type.underscore}/#{activity.action}", activity.trackable_type.underscore.to_sym => activity.trackable
Hi Ryan,
How do i skip the devise login/logout updates in activities table?
Nice article pure informative and knowledgeable thank you for sharing it.
Thanks