Override just the default scope (specifically order) and nothing else in Rails
Asked Answered
V

2

15

So basically I have two classes, Book and Author. Books can have multiple authors and authors can have multiple books. Books have the following default scope.

default_scope :order => "publish_at DESC"

On the Author show page I want to list all the books associated with that author so I say the following...

@author = Author.find(params[:id])
@books = @author.books

All is good so far. The author#show page lists all books belonging to that author ordered by publication date.

I'm also working on a gem that is able to sort by the popularity of a book.

@books = @author.books.sort_by_popularity

The problem is that whenever it tries to sort, the default_scope always gets in the way. And if I try to unscope it before it will get rid of the author relation and return every book in the database. For example

@books = @author.books.unscoped.sort_by_popularity # returns all books in database

I'm wondering if I can use the ActiveRelation except() method to do something like this (which seems like it should work but it doesn't. It ignores order, just not when it is a default_scope order)

def sort_by_popularity
  self.except(:order).do_some_joining_magic.order('popularity ASC')
  #    |------------|                       |---------------------|
end

Any ideas as to why this doesn't work? Any ideas on how to get this to work a different way? I know I can just get rid of the default_scope but I'm wondering if there another way to do this.

Vientiane answered 16/3, 2012 at 23:45 Comment(0)
I
31

You should be able to use reorder to completely replace the existing ORDER BY:

reorder(*args)
Replaces any existing order defined on the relation with the specified order.

So something like this:

def self.sort_by_popularity
  scoped.do_some_joining_magic.reorder('popularity ASC')
end

And I think you want to use a class method for that and scoped instead of self but I don't know the whole context so maybe I'm wrong.

I don't know why except doesn't work. The default_scope seems to get applied at the end (sort of) rather than the beginning but I haven't looked into it that much.

Illume answered 16/3, 2012 at 23:58 Comment(4)
Thank you! That was so easy. This is exactly what I was looking for.Vientiane
Agreed, this was a lifesaver. Never heard of reorder until now.Soapbox
Allow me to third! I changed the order to a reorder in my named scope and it worked like a charm.Punctilious
To completely remove ORDER BY clause from the query, pass nil to reorder: <something>.reorder(nil).<something>Glenine
C
1

You can do it without losing default_scope or other ordering

@books.order_values.prepend 'popularity ASC'
Countershading answered 22/7, 2018 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.