Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a facebook style news feed in ruby on rails?

I am trying to fuse to objects into a single stream.

I created a new model and controller called Newsfeed. I have it so that newsfeed has_many :messages and has_many :images and images and messages belong_to :newsfeed. I am setting up the controller right now and I have:

def index
    @messages = Messages.all
    @images = Images.all
    @feed = ?
end

How do I fuse them so it can pull from both and put them in chronological order when each requires separate formating?

Any help would be greatly appreciated, thanks.

like image 804
Cody Brown Avatar asked Feb 11 '10 03:02

Cody Brown


3 Answers

You could do something as :

controller:

def index
    messages = Messages.all
    images = Images.all
    @feed = (messages + images).order_by(&:created_at)
end

view index.html.erb :

<%= render @feed %>

view _feed.html.erb :

<% if feed.is_a? Message %>
  <%= render "message", :locals => {:message => feed}%>
<% else %>
  <%= render "image", :locals => {:image => feed}%>
<% end %>

And add 2 partials _message.html.erb and _image.html.erb

like image 57
Mike Avatar answered Oct 23 '22 00:10

Mike


I'm sure this can be streamlined from it's ghetto-polymorphism, but it works for me, and I had an identical struggle with the "how do I combine all these model results?!"

I use a separate model, like you, and an observer:

class NewsfeedObserver < ActiveRecord::Observer

  observe Message, Image

  def after_save(object)
    @item = Newsfeed.create(
              :item_id => object.id, 
              :item_type => object.class.to_s
    )
  end
end

Then in the Newsfeed model you can pull and collate as you like:

def self.get_items
  item_list = []
  items = find(:all, :order => "created_at DESC", :limit => 30)
  items.collect{ |i| i.item_type.constantize.find(i.item_id) }
end

Then just pull them and display in your view however you like.

#Controller
def index
  @feed = Newsfeed.get_items
end

Moving forward I will probably start to pack more into the stream-tracking model in order to save hits on the other tables, but hopefully you get the idea.

like image 33
Eric Avatar answered Oct 23 '22 01:10

Eric


This feels like a bit of a loaded question, and though I don't have the time to dig into it in full, here are some initial thoughts for you:

  1. you can combine the two types of items you want to display into a single array and then sort this array based on a field which ought to be common between the two (created_at comes to mind) to get them into chronological order.

  2. In the view, you can use this single collection to iterate over and then use helpers and unique partials to render the items in the correct format. This could be done by checking the objects type as you are trying to display it in the view.

Something along the lines of:

if item.is_a? Message
  render :partial => 'message', :locals => {:message => item }
elsif item.is_a? Image
  render :partial => 'image', :locals => {:image => item }
end

hopefully this helps some!

EDIT: to combine the arrays, you can simply append them together using the '+' operator, since arrays in Ruby do not need to contain a single object type.

your Messages.all and Images.all calls give you arrays of objects, so just try

@messages + @images 

and you should have your combined array.

EDIT #2: If I were writing this myself, I would probably handle the views somewhat like this:

at the top level, simply pass your feeds collection to a partial which is designed to render a single item:

render :partial => "item", :collection => @feed

this will render the '_item.html.erb' partial for each item in @feed. Also, it will give the partial a local variable named 'item' each time, which is the item in @feed for that particular iteration.

now, I would probably have the '_item' partial be very simple, like this:

<%= display_item(item) %>

where display_item is a helper method outside the view and is implemented with the "is_a?" type logic from above to choose the correct display format for the item.

the reason I would break it up like this is it takes advantage of some nice rails features (pass a collection to a partial instead of looping over the collection yourself) and then pulling the logic out into helpers (or even the models if it makes sense to) is useful because it simplifies your views. keeping logic out of the view whenever possible is generally a good practice as such logic can be hard to test and your views will become very hard to read.

like image 1
Pete Avatar answered Oct 23 '22 00:10

Pete