1

My code is still just in development, not production, and I'm hitting a wall with generating data that I want for some views.

Without burying you guys in details, I basically want to navigate through multiple model associations to get some information at each level. The one association giving me problems is a polymorphic belongs_to. Here are the most relevant associations

Model Post
  belongs_to :subtopic
  has_many :flags, :as => :flaggable

Model Subtopic
  has_many :flags, :as => :flaggable

Model Flag
  belongs_to :flaggable, :polymorphic => true

I'd like to display multiple flags in a Flags#index view. There's information from other models that I want to display, as well, but I'm leaving out the specifics here to keep this simpler.

In my Flags_controller#index action, I'm currently using @flags = paginate_by_sql to pull everything I want from the database. I can successfully get the data, but I can't get the associated model objects eager-loaded (though the data I want is all in memory). I'm looking at a few options now:

  • rewrite my views to work on the SQL data in the @flags object. This should work and will prevent the 5-6 association-model-SQL queries per row on the index page, but will look very hackish. I'd like to avoid this if possible

  • simplify my views and create additional pages for the more detailed information, to be loaded only when viewing one individual flag

  • change the model hierarchy/definitions away from polymorphic associations to inheritance. Effectively make a module or class FlaggableObject that would be the parent of both Subtopic and Post.

I'm leaning towards the third option, but I'm not certain that I'll be able to cleanly pull all the information I want using Rails' ActiveRecord helpers only.

I would like insight on whether this would work and, more importantly, if you you have a better solution

EDIT: Some nit-picky include behavior I've encountered

@flags = Flag.find(:all,:conditions=> "flaggable_type = 'Post'", :include => [{:flaggable=>[:user,{:subtopic=>:category}]},:user]).paginate(:page => 1)

=> (valid response)


@flags = Flag.find(:all,:conditions=> ["flaggable_type = 'Post' AND 
  post.subtopic.category_id IN ?", [2,3,4,5]], :include => [{:flaggable=>
  [:user, {:subtopic=>:category}]},:user]).paginate(:page => 1)

=> ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :flaggable
Eric Hu
  • 18,048
  • 9
  • 51
  • 67

2 Answers2

1

Issues: Count over a polymorphic association.

@flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1)

Try like the following:

@flags = Flag.find(:all,:conditions => ["flaggable_type = 'Post' AND post.subtopic.category_id IN ?",
[2,3,4,5]], :include => [{:flaggable => [:user, {:subtopic=>:category}]},:user])
.paginate(:page => 1, :total_entries => Flag.count(:conditions => 
["flaggable_type = 'Post' AND post.subtopic.category_id IN ?", [2,3,4,5]]))
tony
  • 11
  • 1
1

Don't drop the polymorphic association. Use includes(:association_name) to eager-load the associated objects. paginate_by_sql won't work, but paginate will.

@flags = Flag.includes(:flaggable).paginate(:page => 1)

It will do exactly what you want, using one query from each table.

See A Guide to Active Record Associations. You may see older examples using the :include option, but the includes method is the new interface in Rails 3.0 and 3.1.

Update from original poster:

If you're getting this error: Can not eagerly load the polymorphic association :flaggable, try something like the following:

Flag.where("flaggable_type = 'Post'").includes([{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page => 1)

See comments for more details.

Jonathan Tran
  • 15,214
  • 9
  • 60
  • 67
  • The reason I'm using paginate_by_sql is because eager loading doesn't work with polymorphic associations. I just double checked to be sure and got this error `Can not eagerly load the polymorphic association :flaggable` – Eric Hu May 27 '11 at 15:29
  • What version of rails/will_paginate are you using? I tested this on Rails 3.0.7 and will_paginate 2.3.15 before I posted this answer. will_paginate looks for a missing `add_limit!` method, but after hacking that, it worked fine. – Jonathan Tran May 27 '11 at 20:16
  • I'm on rails 3.0.7 and will_paginate 3.0.pre2. I tried without will_paginate, `g = Flag.includes(:flaggable).all` and that worked fine. Adding in the will_paginate call created the error. What did you do with `add_limit!` ? – Eric Hu May 27 '11 at 23:32
  • I made a mistake. `@flags = Flag.includes(:flaggable).paginate(:page => 1)` works on 3.0.pre2. But using `paginate_by_sql` doesn't. Can you switch to use `paginate`? – Jonathan Tran May 28 '11 at 03:50
  • 1
    After searching for your error message, it occurred to me that you're probably leaving out a relevant part of your query from your posted question, b/c the code in my answer does work. Check out [this](http://stackoverflow.com/questions/2079867/activerecordeagerloadpolymorphicerror-can-not-eagerly-load-the-polymorphic-ass) and [this](http://stackoverflow.com/questions/680141/activerecord-querying-polymorphic-associations). Can you update your question with your full query/paginate_by_sql statement? – Jonathan Tran May 30 '11 at 17:36
  • I get the error even without a paginate_by_sql command, I.E. `g = Flag.includes(:flaggable).paginate(:page => params[:page])`. To answer your question, though, I'll include my paginate_by_sql command in a new comment (you'll see why). I should add that I didn't use `paginate_by_sql` until after I couldn't get `includes` to work with paginate. – Eric Hu May 31 '11 at 18:43
  • `@flags = Flag.paginate_by_sql([ "SELECT f.id, f.user_id, f.flaggable_id, f.created_at, f.flaggable_type, u.display_name, p.post_content, c.category_name FROM flags f INNER JOIN users u ON (f.user_id = u.id) INNER JOIN posts p ON (f.flaggable_type = 'Post' AND (f.flaggable_id = p.id)) INNER JOIN subtopics s ON (p.subtopic_id = s.id) INNER JOIN categories c ON (c.id IN (?) AND (s.category_id = c.id))", user_moderated_categories ], :page => ...` – Eric Hu May 31 '11 at 18:44
  • One of the links you provided was a good start. I can do `g = Flag.joins("INNER JOIN posts p ON p.id = flags.flaggable_id AND flags.flaggable_type = 'Post'").paginate(:page => params[:page])`. However, Rails still make a SQL query with `g.first.flaggable`. If I change this to `Flag.includes...`, the `INNER JOIN` string doesn't work. I'll continue playing with this to see if I can get it to work. If not, then the join solution will also require ugly Rails-SQL code to prevent many SQL queries. – Eric Hu May 31 '11 at 20:17
  • I'm not sure why `includes` works for you but not me. I found a workaround, though: `Flag.find(:all,:conditions=>"flaggable_type = 'Post'", :include => [{:flaggable=>[:user, {:subtopic=>:category}]}, :user]).paginate(:page =>5)`. This eager-loads everything I want, but only for one association type. I can live with that. I appreciate your help and I'll mark your answer as accepted if you could edit your answer to include this code snippet so others won't have to read through these comments – Eric Hu May 31 '11 at 20:48
  • I feel silly now. This query solves the issue of eager loading while using will_paginate, but I forgot that I want to filter Flag objects based on their Category (either (flaggable)post.subtopic.category or (flaggable)subtopic.category). Perhaps I should just start a new question – Eric Hu Jun 01 '11 at 00:04
  • 1
    I'm not sure. I understand you want to use ActiveRecord and not drop down into SQL, but AR provides a way to drop into SQL b/c sometimes it's just easier that way. Have you looked into using [subqueries](http://stackoverflow.com/questions/5483407/subqueries-in-activerecord)? Sometimes it makes your SQL simpler. Also, in the future, instead of commenting huge chunks of code, you should edit your question and add updates to it, especially when it's relevant to your whole question. Comment on an answer when it's relevant to that specific answer or you want to notify the answerer. – Jonathan Tran Jun 01 '11 at 00:35