Search code examples
ruby-on-railswill-paginate

Add an element to a Rails will_paginate collection


I have a Location model that has_many LocationPhotos. The Location also belongs_to a single LocationPhoto as the featured_location_photo. The belongs_to association looks like this:

class Location < ActiveRecord::Base
  belongs_to :featured_location_photo, class_name: 'LocationPhoto', foreign_key: 'featured_location_photo_id'
end

Using will_paginate, I can paginate the photos for a location like this:

location.location_photos.paginate(page: 1, per_page: 4)

Ideally, I would like to always include the featured_location_photo as the first element, on the first page, of the collection that's returned, and not be included again. So, for example, if the related photos have ids 1,2,3,4,5,6 and the featured photo has id 3, I would want page one to show photos with ids:

3, 1, 2, 4

And page two to show photos with ids:

5, 6

The collection should also show total_entries equal to 6.

Is there any way to do this? I can't think of a way to do it by scoping the query because there's no way to tell the featured photo to sort to the top of the results.

My other thought is to first check if the location has a featured photo. If it does, remove that from the query with a scope:

result = location.location_photos.where.not(id: [featured_photo_id]).paginate(page: 1, per_page: 4)    

Then, I would need to add the featured photo back into the front of result.


Solution

  • I'm totally saving this, because I've needed to solve this problem before and always copped out on the solution. Thanks for making me find this!!!

    To promote a specific record in a collection use:

    order("case when id = #{id} then 0 else id end")
    

    Which means you can just create a custom scope like so:

    class Location < ActiveRecord::Base
      def photos
        location_photos.
          order("case when id = #{featured_location_photo_id} then 0 else id end")
      end
    end
    

    EDIT: actually, ActiveRecord relations respond to unshift just like arrays. So all you actually need is this:

    def photos
      location_photos.unshift( featured_location_photo )
    end
    

    Which of course you can then paginate.