rails 3 + active record: is there a clever way to "round robin" the order of records
(the actual numbers are 100x larger, but its easier to explain as follows)
we have a record set of 100 reviews for 5 restaurants. Some restaurants have 3 or 4 reviews, some have 20-30 reviews.
currently I have a simple report that arranges them from newest to oldest:
@reviews = Review.joins(:store).
where('reviews.published= ?',true).
order("reviews.created_at DESC")
For one of my report views, I need to display the reviews in round-robin order that ensures each restaurant gets equal coverage at the top of the list.
most recent for restaurant #A
most recent for restaurant #B
...
most recent for restaurant #E
then repeat for the next-most-recent for #A, #B,... #E
etc
Ideally the order of the round-robin would be which restaurant has the most total reviews.
It's not hard to do it the slow way in code, but I'm wondering if there's a faster/better activerecord approach?
===
Is there a way to (a) sort by restaurant, (b) create a temporary "counter" column that counts from 1 to N for each entry for each restaurant, (c) re-sort on that counter column? That would do it.
For example, after (a) and (b) the recordset would be
A , <some review>, 1
A , <some review>, 2
...
A , <some review>, 23
B , <some review>, 1
B , <some review>, 2
...
B , <some review>, 11
C , <some re开发者_如何转开发view>, 1
C , <some review>, 2
...
C , <some review>, 9
If I could then sort that recordset on the counter, it would round-robin A,B,C, etc
You can add a column in the DB last_displayed
, update that with each display of the restaurant and always sort .order('last_displayed')
.
I also think you could use the updated_at
column, touch
the record after displaying it, and sort by that column - this way you won't need to update your DB structure
I recently implemented something similar to this. In my model I created this method:
def self.order_jobs
ordered_jobs = []
jobs = Job.all.to_a
jobs.each do |j|
ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Redhat'}) unless (jobs.find_index {|j| j.company == 'Redhat'}).nil?
ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Zapier'}) unless (jobs.find_index {|j| j.company == 'Zapier'}).nil?
ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Mozilla'}) unless (jobs.find_index {|j| j.company == 'Mozilla'}).nil?
ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Ubuntu'}) unless (jobs.find_index {|j| j.company == 'Ubuntu'}).nil?
ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Digital Ocean'}) unless (jobs.find_index {|j| j.company == 'Digital Ocean'}).nil?
end
ordered_jobs
end
Then in the controller I simply call Job.order_jobs like this:
def index
@jobs = Job.order_jobs
end
In the order_jobs method I start by creating two arrays. One with the existing jobs in the DB (jobs) and another (ordered_jobs) where I will move the jobs to in a round robin order. The magic is in ordered_jobs.push jobs.delete_at(jobs.find_index {|j| j.company == 'Redhat'})
; this says find a job with the company Redhat and move it from the jobs array to the ordered_jobs array. the unless
and .nil?
sillyness allows you to move past that code once the jobs array no longer has elements that fit the given criteria.
精彩评论