1

I have a Video model which has_many :comments and has_many :reviews.

I'd like to build a an API which allows for returning both association types intermixed and ordered by creation time.

In other words, is there a way (without it being inefficient or hacky) I could call video.feedback and get back a mixed list of comments and reviews?

I'd also like the video controllers' index action to return a list of videos each with thier own top three most recent feedbacks (could be comments, could be reviews, could be a mixture of both all depending on when they were created)

Mark Murphy
  • 2,834
  • 1
  • 31
  • 29
  • Add a method to the model that concatenates the two and then sorts them... e.g, `(comments.all + reviews.all).sort_by{ |e| e.created_at }` – Jon Egeland Feb 24 '15 at 20:30

1 Answers1

2

You can make a method in your model that concatenates the two associations and then sorts them. For example:

def feedback
  results = (comments + reviews).sort_by{ |e| e.created_at }
end

If you have a lot of records and want to speed up the process a little bit, you can easily limit the number of results that are returned. Note that you need to order each association independently first, then merge, then sort again.

def feedback max
  # We need to get `max` entries from each to ensure that we will have
  # enough entries in the final result.
  results = comments.order(:created_at).limit(max) + reviews.order(:created_at).limit(max)

  # Then, we only return the first `max` entries of the result, since there
  # will be `2*max` entries in `results`.
  results.sort_by{ |e| e.created_at }.first(max)
end

This leads into the second part of your question.


If you just want the most recent feedback available in the view, then you don't actually need to change your controller action. Instead, you can just access them in the view directly.

Assuming you keep the list of videos in the variable @videos, this could look like (omitting any ERB/Haml you may be using):

@videos.each do |video|
  video.feedback(3).each do |fb|
    # do whatever with each feedback item here
  end
end

If you need to differentiate between the types of feedback, you can use a case...when block:

case fb
when Comment
  # The feedback is a comment
when Review
  # The feedback is a review
end
Jon Egeland
  • 12,470
  • 8
  • 47
  • 62
  • Is there any way to do this at the database level? – Mark Murphy Feb 24 '15 at 21:07
  • @MarkMurphy yes, but it's really not pretty, and also not that much better (see http://stackoverflow.com/questions/1584589/rails-union-hack-how-to-pull-two-different-queries-together). For the sake of simplicity, clarity, and abstraction, it's better to just do it in the model. – Jon Egeland Feb 24 '15 at 22:05
  • I'm always concerned with making things as fast as possible but obviously I don't what it to be ugly either because it's difficult to maintain among other things. Your solution looks fairly clean but the fact that it processes through the model would obviously have a performance impact. Question is, how inefficient is it really? – Mark Murphy Feb 25 '15 at 13:57
  • I'm wondering if there's an alternate way to solve it though some type of single/multiple table inheritance jig. – Mark Murphy Feb 25 '15 at 13:59
  • @MarkMurphy It's not really *inefficient* unless you're trying to sort some millions of records. That's part of why we have the `max` limitation on the `feedback` method, too: to make sure we *don't* end up sorting millions of records. I think you're trying to optimize before you've tried the existing solution. If it's not fast enough *in practice*, then consider moving it to the database level. – Jon Egeland Feb 25 '15 at 14:02
  • @MarkMurphy Another option is to store `comments` and `reviews` in one polymorphic table, `feedbacks`. Then the operation is as simple as `FeedBack.order(:created_at).first(max)`. – Jon Egeland Feb 25 '15 at 14:03
  • That's what I'd like to be able to do and I had thought of that but they really aren't related in any way other than the timestamps and owner fields and are already in their own separate tables. Is there a way to keep them in their seperate tables and still do this: `FeedBack.order(:created_at).first(max)` or are we still talking ugly situation? – Mark Murphy Feb 25 '15 at 14:10
  • @MarkMurphy if they aren't related, then it's going to be hacky. ActiveRecord isn't powerful enough to support complex queries with unions and sorting their results. If you're dead-set on doing it in the database, check out [this article on a "union hack"](https://coderwall.com/p/9hohaa/activerecord-union-hack), but understand that this really is a hack and, in reality, won't have any real performance impact. – Jon Egeland Feb 25 '15 at 14:31