Ok, so I have this Concern
which contains a method top
for retrieving records order by the number of times someone has listened to them (directly if the record is a song, or via songs if it's something else, like a genre or artist). In case of a tie, it orders records by the popularity on other sites.
The following code works almost perfectly. It returns an array of the objects, in the correct order. My main issue is that I get an array and not a relation. So the caller cannot further add stuff like Song.top.limit 3
or Genre.top.offset(10).limit(5)
.
Here's my method:
def top(options = {})
tname = self.name.tableize
inner_select = self.select("#{tname}.*,COUNT(DISTINCT(listens.id)) AS listens_count")
inner_select = inner_select.joins(:songs) unless self.name == 'Song'
inner_select = inner_select.joins("LEFT JOIN listens ON listens.song_id = songs.id")
inner_select = inner_select.where("listens.user_id = ?", options[:for].id) if options[:for].is_a? User
inner_select = inner_select.group("#{tname}.id").to_sql
# this is the part that needs fixin'
find_by_sql("
SELECT
#{tname}.*,
#{tname}.listens_count,
SUM(sources.popularity) AS popularity_count
FROM (#{inner_select}) AS #{tname}
LEFT JOIN sources ON
sources.resource_id = #{tname}.id
AND
sources.resource_type = '#{self.name}
GROUP BY #{tname}.id
ORDER BY listens_count DESC, popularity_count DESC
")
end
Here are the SQL queries that are generated, as requested. This is from Song.top
:
SELECT
songs.*,
songs.listens_count,
SUM(sources.popularity) AS popularity_count
FROM (SELECT songs.*,COUNT(DISTINCT(listens.id)) AS listens_count FROM "songs" LEFT JOIN listens ON listens.song_id = songs.id GROUP BY songs.id) AS songs
LEFT JOIN sources ON
sources.resource_id = songs.id
AND
sources.resource_type = 'Song'
GROUP BY songs.id
ORDER BY listens_count DESC, popularity_count DESC
This is from Artist.top
:
SELECT
artists.*,
artists.listens_count,
SUM(sources.popularity) AS popularity_count
FROM (SELECT artists.*,COUNT(DISTINCT(listens.id)) AS listens_count FROM "artists" INNER JOIN "artists_songs" ON "artists_songs"."artist_id" = "artists"."id" INNER JOIN "songs" ON "songs"."id" = "artists_songs"."song_id" LEFT JOIN listens ON listens.song_id = songs.id GROUP BY artists.id) AS artists
LEFT JOIN sources ON
sources.resource_id = artists.id
AND
sources.resource_type = 'Artist'
GROUP BY artists.id
ORDER BY listens_count DESC, popularity_count DESC
So I found the answer, turns out I had completely missed the from
method. I'll post the final method here just in case anyone else is as blind as me:
def top(options = {})
tname = self.name.tableize
inner_select = self.select("#{tname}.*,COUNT(DISTINCT(listens.id)) AS listens_count").
joins("LEFT JOIN listens ON listens.song_id = songs.id").
group("#{tname}.id")
inner_select = inner_select.joins(:songs) unless self.name == 'Song'
inner_select = inner_select.where("listens.user_id = ?", options[:for].id) if options[:for].is_a? User
inner_select = "(#{inner_select.to_sql}) as #{tname}"
select("#{tname}.*,#{tname}.listens_count,SUM(sources.popularity) AS popularity_count").
from(inner_select).
joins("LEFT JOIN sources ON sources.resource_id = #{tname}.id AND sources.resource_type = '#{self.name}'").
group("#{tname}.id").
order("listens_count DESC, popularity_count DESC")
end
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.