Search code examples
ruby-on-railspolymorphic-associations

Cleanest way to query polymorphic association?


Problem: I have a working solution, but have a sneaking suspicion that it's probably quite calculation heavy. Would love to hear any more efficient ways to achieve this.

Goal: I want to pull a list of books, ordered by priority, where the book is EITHER polymorphically associated with school or class.

  • Schools: have many classes, through topics
  • Classes: have one school, through topics
  • Books: bookable by either classes or schools

Current class view file:

<%  @school_class_books.order("priority ASC").each do |book| %>
    <b><a href="<%= book.url %>"><%= book.name %></a></b><br />
    <%= book.long %><br /><br />
<% end %>

Current class.rb controller:

@school_class_books = Book.all.where(bookable_type: ["School","Class"],
 bookable_id: [@class.school.id,@class.id])

Solution

  • Before any performance issues are considered, you should know that your code as written will cause bugs by finding Books that should not be found.

    For example, if @school.id = 10 and @class.id = 15, this query will return a book with its bookable fields set to: bookable_id = 15 and bookable_type = "School".

    That book belongs to a different school!

    It may be simpler to do:

    @books = Book.where(bookable: @school).to_a.concat(Book.where(bookable: @class).to_a)

    This is polymorphic syntax sugar for:

    @books = Book.where(bookable_type: @school.class.to_s, bookable_id: @school.id).to_a.concat(Book.where(bookable_type: @class.class.to_s, bookable_id: @class.id).to_a)

    In other words, simply do two lookups and combine the results.

    As for performance, using syntax like where(my_attribute: [value1, value2, value3]) will produce SQL like WHERE books.my_attribute IN (value1, value2, value3).

    SQL statements using IN can be inefficient because they make it harder for the database server to use indexes on the my_attribute field (in this case bookable_id).

    Finally, you should consider renaming your Class model to Course to avoid namespace collisions with Ruby's class keyword, or programmer confusion when reading variable names. Consider the awkwardness of bookable_type: @class.class.to_s