3

I have one model which has a polymorphic association with three other tables:

class User < ActiveRecord::Base
    belongs_to :authenticatable, :polymorphic => true


# == Schema Information
#
# Table name: employees
#
#  id                     :integer(4)      not null, primary key
#  ...
#  school_id              :integer(4)

class Guardian < ActiveRecord::Base
    has_one :user, :as => :authenticatable
    belongs_to :school


# == Schema Information
#
# Table name: guardians
#
#  id                     :integer(4)      not null, primary key
#  ...
#  school_id              :integer(4)

class Guardian < ActiveRecord::Base
    has_one :user, :as => :authenticatable
    belongs_to :school

# == Schema Information
#
# Table name: students
#
#  id                     :integer(4)      not null, primary key
#  ...
#  school_id              :integer(4)

class Student < ActiveRecord::Base
    has_one :user, :as => :authenticatable
    belongs_to :school

As you can see, the last 3 models belong to a "school" model and therefore have a column named school_id. I want to retrieve all rows from user such that their corresponding authenticatable school_id is equal to some value. To clarify, I'd like to do something like this:

User.joins(:authenticatable).where(:school_id => some_value)

As it is, that will result in an

ActiveRecord::EagerLoadPolymorphicError

on a final note, i managed to look up some documentation which suggests that using include on a polymorphic association should work, such as:

User.find(:all, :include => :authenticatable) (This works)

However if I do this:

User.find(:all, :include => :authenticatable).where(:school_id => some_value)

It breaks rails, because User.find(...) returns an Array and the where method is not defined for that class.

I've tried some other options and have not found a way to accomplish what I want. Can you help me? Thanks!

sauronnikko
  • 4,665
  • 5
  • 31
  • 47
  • http://stackoverflow.com/questions/2079867/activerecordeagerloadpolymorphicerror-can-not-eagerly-load-the-polymorphic-ass – mark Aug 23 '11 at 14:21

2 Answers2

1

You can try to solve it by using SQL query in joins statement:

Model1.joins("INNER JOIN model2 ON model2.id = model1.polymorphizm_id INNER JOIN model3 ON model3.id = model1.polymorphizm_id INNER JOIN model4 ON model4.id = model1.polymorphizm_id").where("model2.column_id = ... or model3.column_id = ... or model4.column_id = ...")

I not actually try it, but polimorphic assoc. adds 2 columns to model: xxx_type and xxx_id. They serve to handle assoc. with multiple models.

bor1s
  • 4,081
  • 18
  • 25
  • Thanks for your answer. What you proposed did not work. It returned me no rows. I've updated my question so that it's more understandable – sauronnikko Aug 23 '11 at 15:10
  • so, I have read your post. Have you tried?: `User.include(:authenticatable).where(:school_id => some_value)` thisshould return ActiveRelation object and then you can call `.all` ro any enumerable method on it. Please let me know if it helped or not – bor1s Aug 23 '11 at 15:48
  • `User.include(:authenticatable).where(:school_id => some_value)` gives me the following error: `NoMethodError: private method 'include' called for #` I tried with `includes` (plural). It said this: `Mysql2::Error: Unknown column 'users.school_id' in 'where clause':...` – sauronnikko Aug 23 '11 at 16:18
  • yes really `includes` :), just my typo. So you are on the right way. Do you have school_id column in Users table? – bor1s Aug 23 '11 at 16:47
  • I've updated my question again with more information about the models. To answer yours, no, User does not have a `school_id` column. Remember that I want to retrieve all users which have a specific `authenticatable` `school id`. The `school_id` column exists in those other models (Employee, Guardian and Student) but not on User. User just has a polymorphic association with them. – sauronnikko Aug 23 '11 at 18:44
  • so try something like this: `User.find(:all, :include => :authenticatable).where("employee.school_id = ? OR guardians.school_id = ? OR students.school_id = ?", your_value, your_value, your_value)` – bor1s Aug 23 '11 at 18:47
  • Your code gives me the following error: `NoMethodError: undefined method 'where' for #` I tried also this alternative: `User.includes(:authenticatable).where("employee.school_id = ? OR guardians.school_id = ? OR students.school_id = ?", value, value, value)` and it gave this error: `ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :authenticatable` – sauronnikko Aug 23 '11 at 18:58
  • i see, just rework my code in the post for your models, look for `shcool_id` column in your employee, guardians, etc .. tables and not in User table – bor1s Aug 23 '11 at 19:17
0

You could futz around with sub-queries and maybe get something working:

User.where(authenticable: Guardian.where(school_id: some_value))

If you're using Rails 5, they added .or to ActiveRecord, so you might be able to join some together like:

User.where(authenticable: Guardian.where(school_id: some_value))
    .or.where(authenticable: Student.where(school_id: some_value))

merge is another method that might yield some results:

User.all.merge(Guardian.where(school_id: some_value))

I'm not too familiar with querying across polymorphic tables, so your mileage may vary with the above. Worst case, you may end up having to do more than one query to preserve the scope:

user_ids = Guardian.joins(:user).select('users.id').where(school_id: some_value).pluck(:user_id) +
           Student.joins(:user).select('users.id').where(school_id: some_value).pluck(:user_id)
users = User.where(id: user_ids)

You may be stuck with the schema now, but if you're at liberty to change it, it looks like you might be better off with a single-table inheritance relationship:

class User < ActiveRecord::Base
  belongs_to :school
end

class Student < User
end

class Guardian < User
end

Then you simply say:

User.where(school_id: some_value)

I've found polymorphic relations to be more trouble than they're worth in many cases due to how difficult it is to do things like queries and bulk updates, as well as making it impossible to use foreign key constraints. With some extra thought, there's usually a reasonable data model that works well without them.

lobati
  • 9,284
  • 5
  • 40
  • 61