Rails: Structuring a query involving a polymorphic association and STI
Asked Answered
V

1

4

I'm trying to find the 10 most recent comments on photos so I can integrate them into an activity feed on my Rails 3.0.3 application.

I've got a Photo model, which inherits from an Upload model using single table inheritance:

class Upload < ActiveRecord::Base
    ...
end

class Photo < Upload
    has_many :comments, :as => :commentable
    ...
end

The polymorphic association commentable is described in the Comment model:

class Comment < ActiveRecord::Base
    belongs_to :commentable, :polymorphic => true
end

So far so good, right? The problem comes when I try to construct a query. After some trial and error, I came up with this code that sits in the Photo model:

def self.latest_comments(count = 10)
    Comment.where(:commentable_type => "Upload")\
       .joins("INNER JOIN uploads ON comments.commentable_id = uploads.id")\
       .where("uploads.type" => "Photo").order("comments.created_at DESC").limit(count)
end

This code works on my development machine with SQLite, but I've had to make some changes to get it to work on the production server using PostgreSQL. The above incarnation of the query hasn't been tested on the production server yet, but I was looking for a cleaner way of structuring the query, as the above doesn't feel very robust, nor does it seem very 'Railsy'.

What I'd like is to be able to say is

Comment.joins(:commentable => :photo)

...but this raises an exception, presumably because Rails doesn't know how to do the join:

ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :commentable

I came upon a post on StackOverflow that described a different way of querying a polymorphic association. I was able to come up with the following code based upon this post:

Comment.find_all_by_commentable_type("Upload", :include => :commentable,
        :order => "created_at DESC", :limit => 10)

However, this doesn't work for STI: since Photo is inheriting from Upload, I can't directly fetch the comments pertaining to photos - only the comments for all classes inheriting from Upload.

I've looked at a fair number of other posts relating to either polymorphism or STI, but none combine the two in the way I'm looking for. I could just muddle around with the initial query, but does anyone have any thoughts on alternative ways to structure a query involving polymorphism and STI?

Edit: I found another question on here along the same lines as mine. Unfortunately, the top answer didn't provide the solution I'm looking for. If I were to apply it to my problem I'd be moving the has_many :comments association from the Photo model to the Upload model, and adding some code to the Comment class to determine the correct class of commentable. This isn't ideal as it breaks the modelling in that any subclass of Upload will have comments. There must be a better way...?

Vaudevillian answered 5/7, 2011 at 12:23 Comment(0)
M
1

You are providing the association to comments on the Upload class. Photo, which is inheriting from Upload will have this association through inheritance. Since the comments method is available on both the Upload instance, and on the Photo instance, you can write the following method to retrieve the comments:

# app/models/upload.rb

def latest_comments(count=10)
  self.comments.order("created_at DESC").limit(count)
end

This will join to the comments table using the type "Upload" when working with an instance of Upload, and it will join using the type "Photo" when using working with instances of that class. Rails will automatically handle the join to the comments table using the class name when going through the comments association.

Marteena answered 10/7, 2011 at 0:58 Comment(1)
Thanks for the reply Ben, but my model specifies that only Photos, and not Uploads, are commentable. It greatly simplifies things if the superclass Upload is given the association has_many :comments, but I don't want to break my model by doing this. If I don't want to disrupt my model in that way, is what I want to achieve actually possible without a long chained query, I wonder?Vaudevillian

© 2022 - 2024 — McMap. All rights reserved.