views:

204

answers:

4

In my application I need to show a list of songs. Right now I'm doing this:

Song.all.sort {|x,y| x.artist.name <=> y.artist.name }

Unfortunately, this means that "The Notorious B.I.G" will sort with the T's while I want him to sort with the N's (i.e., I want to ignore articles -- "the", "a", and "an" -- for the purposes of sorting.

My first thought was to do this:

Song.all.sort {|x,y| x.artist.name.gsub(/^(the|a|an) /i, '') <=> y.artist.name.gsub(/^(the|a|an) /i, '') }

But it doesn't seem to work. Thoughts?

A: 

You may be better off doing this in SQL,

SELECT Title,
CASE WHEN SUBSTRING_INDEX(Title, ' ', 1)
  IN ('a', 'an', 'the')
 THEN CONCAT(
  SUBSTRING(Title, INSTR(Title, ' ') + 1),
  ', ',
  SUBSTRING_INDEX(Title, ' ', 1)
 )
 ELSE Title
END AS TitleSort
FROM music
ORDER BY TitleSort

There is an article describing this in more detail as well.

The big benefit over the approach you have laid out is that you're pulling ALL of the records out first which will screw you up in subtle ways both performance and user interface wise (I'm thinking of situations like trying to page through a large set of songs).

Mike Buckbee
A: 

Your answer seems to be correct but probably u can change it to:

Song.all.sort {|x, y| x.artist.name.sub(/^(the|a|an)\s/i, '') <=> y.artist.name.sub(/^(the|a|an)\s/i, '') }

changed to sub because you only need to change it in the beginning and not the whole string, space changed to \s to indicate a space.

you can go to http://www.rubyxp.com/ to test your regex

if it's still not working then probably some records are not coming out the way they should? like spaces at the start of the string, nil or blank title, etc...

hope it helps =)

Staelen
+8  A: 

My favorite approach to these kind of problems is to store an extra sort_order column in the database.

That way when you have 10000 songs that you would like to page through, you can do that in SQL and avoid having to pull them all back.

Its simple to add a before_save filter to keep this column in sync.

The cleanish solution, without schema changes is:

class Artist
  def sortable_name
    self.name.sub(/^(the|a|an)\s+/i, '')
  end
end

class Song
  def sortable_name
    # note the - is there so [Radio] [head on] and [Radiohead] [karma police] 
    #   are not mixed in the ordering
    "#{artist.sortable_name} - #{name}" 
  end
end

# breaks ties as well 
Song.all.sort_by { |song| song.sortable_name }
Sam Saffron
Storing an extra column lets you do some other nice things, like .downcase it, replace ä with ae and the like (if appropriate), reverse first/last names, etc., all without recomputing everything during every query...
glenn mcdonald
you can also be more concise with symbol_to_proc but I personally dislike that trick ...
Sam Saffron
Looks good, though what do you mean by "in cases like 'the the'..."?
Horace Loeb
Bad example: I mean that if you do not have some sort of delimiter between the artist and song name you may confuse your ordering in some weird cases.
Sam Saffron
But you could use "-" instead of "---", right?
Horace Loeb
yerp Id imagine that would be fine
Sam Saffron
A: 

Break it up!

class Artist < ActiveRecord::Base

  def <=>(other)
    name.gsub(/^(the|a|an) /i, '') <=> other.name.gsub(/^(the|a|an) /i, '')
  end

end

songs = Song.all(:include => :artist)
songs.sort_by { |s| s.artist }
hgimenez
For some reason, code formatting is eating up full lines of code above. Please see pastie here: http://pastie.org/612206
hgimenez
nevermind, issue resolved :/
hgimenez