Rails - Sort by join table data
I would suggest to avoid using default_scope
, especially on something like price on another table. Every time you'll use that table, join and ordering will take place, possibly giving strange results in complex queries and anyway making your query slower.
There's nothing wrong with a scope of its own, it's simpler and it's even clearer, you can make it as simple as:
scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
PS: Remember to add an index on price
; Also see other great answers in here to decide between join/include.
@ecoologic answer:
scope :ordered, -> { includes(:availabilities).order('availabilities.price') }
is great, but it should be mentioned that includes
could, and in some cases should be replaced by joins
. They both have their optimal use cases (see: #1, #2).
From practical standpoint there are two main differences:
includes
loads associated record(s); in this caseAvailability
records.joins
don't load any associated record(s). So you should useincludes
when you want to use data from join model e.g. displayprice
somewhere. On the other hand,joins
should be used if you intend to use join model's data only in query e.g. inORDER BY
orWHERE
clauses.includes
loads all records, whilejoins
loads only those records that have associated join model. So in OP's case,Home.includes(:availabilities)
would load all homes, whileHome.joins(:availabilities)
would load only those homes that have associated at least one availability.
Also see this question.
Another way to achieve this:
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price]) }
You can also specify ASC
direction with
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].asc) }
DESC
:
scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].desc) }
Using arel_table
on ActiveRecord
model makes you save against scenario when table name changed (but it happens very rarely).
Note that it is nice to add main_table#id
for determinate sorting.
So final version would be:
scope :ordered, -> {
includes(:availabilities).
order(Availability.arel_table[:price].asc, order(Home.arel_table[:id].asc)
}
Figured it out with help from this related post.
I moved the ordering out of the Home model and into the Availability model:
Availability
default_scope :order => "price ASC"
Then I eager loaded availabilities into the Home model and sorted by price:
Home
default_scope :include => :availabilities, :order => "availabilities.price ASC"