16

I am trying to learn rails [by following the SAAS course in coursera] and working with simple Movie table using ActiveRecord.

I want to display all movies with title sorted. I would like it to be sorted case insensitively.

I tried doing it this way:

Movie.all(:conditions => ["lower(title) = ?", title.downcase],:order => "title DESC")
=>undefined local variable or method `title' for #<MoviesController:0xb4da9a8>

I think it doesnt recognise lower(title) .

Is this the best way to achieve case insesisitve sort ?

Thanks!

codeObserver
  • 6,521
  • 16
  • 76
  • 121

4 Answers4

23

Use where and not all

Movie.where("lower(title) = ?", title.downcase).order("title DESC")

Don't really understand the sort though. Here you'll get all movies with lower title equalling to title.downcase. Everything is equal, how could you sort it by title desc ?

To sort reverse-alphabetically all movies by lowercase title :

Movie.order("lower(title) DESC").all
Anthony Alberto
  • 10,325
  • 3
  • 34
  • 38
  • 2
    In Rails 6 this won't be allowed anymore, make sure to wrap any SQL like these in Arel.sql() (https://github.com/rails/rails/commit/310c3a8f2d043f3d00d3f703052a1e160430a2c2) – Nicolas Connault Mar 02 '19 at 02:37
9

You have to do this:

Movie.order("lower(title) DESC").all
Dogbert
  • 212,659
  • 41
  • 396
  • 397
6

A more robust solution is to use arel nodes. I'd recommend defining a couple scopes on the Movie model:

scope :order_by_title, -> {
  order(arel_table['title'].lower.desc)
}

scope :for_title, (title)-> {
  where(arel_table['title'].lower.eq title.downcase)
}

and then call Movie.for_title(title).order_by_title

The advantage over other answers listed is that .for_title and .order_by_title won't break if you alias the title column or join to another table with a title column, and they are sql escaped.

Like rickypai mentioned, if you don't have an index on the column, the database will be slow. However, it's bad (normal) form to copy your data and apply a transform to another column, because then one column can become out of sync with the other. Unfortunately, earlier versions of mysql didn't allow for many alternatives other than triggers. After 5.7.5 you can use virtual generated columns to do this. Then in case insensitive cases you just use the generated column (which actually makes the ruby more straight forward).

Postgres has a bit more flexibility in this regard, and will let you make indexes on functions without having to reference a special column, or you can make the column a case insensitive column.

Garrett Motzner
  • 3,021
  • 1
  • 13
  • 30
3

Having MySQL perform upper or lower case operation each time is quite expensive.

What I recommend is having a title column and a title_lower column. This way, you can easily display and sort with case insensitivity on the title_lower column without having MySQL perform upper or lower each time you sort.

Remember to index both or at least title_lower.

rickypai
  • 4,016
  • 6
  • 26
  • 30