Search code examples
ruby-on-railsrubyruby-on-rails-4delegatesrails-activerecord

In Rails, can I order a query by a delegate method?


I'm having difficulty ordering a query by a delegate method. I've been tasked with helping upgrade a fairly large Rails 3 application to Rails 4. I've come across this query in an index action.

# measurements_controller.rb
def index
  @measurements = Measurement.includes(:identifier).order(:name)
end

In Rails 4, I'm getting this error:

ERROR: column items.name does not exist LINE 1: ...D (item_names.name LIKE '%') ORDER BY "item...

So I took a look at the models and found:

# measurement.rb
class Measurement < Item
  ...
end

# item.rb
belongs_to :item, class_name: 'ItemName'
delegate :name, :name=, to: :item

# item_name.rb
# This object has a name column in the database

If I'm in the terminal and I have an instance of a Measurement, I can easily call the .name method and it works great. But when it comes to .order(:name), that does not work due to the column not existing.

I have found one solution, but it seems to defeat the purpose of the delegate method. In the controller, I can change it to:

@measurements = Measurement.includes(:item).order('item_names.name')

Since I know that Item delegates the name method to ItemNames, I don't think my controller needs to know where it's delegated to.

Is there a way to keep the existing code and still order by the delegate method?


Solution

  • Yes, but it requires instantiating all your records first (which will decrease performance).

    @measurements = Measurement.joins(:item).sort_by &:name
    

    This code loads all Measurements and instantiates them and then sorts them by the Ruby method .name

    Explanation

    delegate only affects instances of your ActiveRecord model. It does not affect your SQL queries.

    This line maps directly to a SQL query.

    Measurement.includes(:item).order(:name)
    

    As you have noticed, it looks for a name column on the measurements table and doesn't find anything. Your ActiveRecord model instances know that name only exists on identifier, but your SQL database doesn't know that, so you have to tell it explicitly on which table to find the column.