Using rails active_record 'includes' changes results of has_many attribute accessor depending on what is included

I have a Project and User model, that are connected through a Permission model with a has_many through, polymorphic association. The Permission table also tracks a pending status. The relevant setup is as follows:

class Project < ActiveRecord::Base
  has_many :permissions, as: :permissionable
  has_many :contributors, through: :permissions, source: :user
  has_many :pending_contributors, through: :permissions, source: :user, conditions: '"permissions"."accepted" = false'
  ...
end

class Permission < ActiveRecord::Base
   belongs_to :user
   belongs_to :permissionable, polymorphic: true
   ...
end

class User < ActiveRecord::Base
  has_many :permissions
  has_many :projects, through: :permissions, source: :permissionable, source_type:'Project'
  ...
end

I'm trying to access both the contributors and pending_contributors lists on projects without causing the N+1 query issue as I render all projects. The trouble is I can't use includes(:contributors, :pending_contributors) because whichever one of the two includes is mentioned first is the only one that works.

For example in my rails console:

u = User.find(2)
u.projects.includes(:contributors, :pending_contributors).each do 
  |p| puts p.contributors.map(&:id).inspect
end   

The result of this in my rails console is:

User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 2]]
Project Load (1.3ms)  SELECT "projects".* FROM "projects" INNER JOIN "permissions" ON "projects"."id" = "permissions"."permissionable_id" WHERE "permissions"."user_id" = 2 AND "permissions"."permissionable_type" = 'Project'
Permission Load (1.1ms)  SELECT "permissions".* FROM "permissions" WHERE "permissions"."permissionable_type" = 'Project' AND "permissions"."permissionable_id" IN (64, 56, 54, 53, 51, 50, 61, 62, 63, 57, 65, 66, 48, 49, 67, 68, 69, 70, 71, 60, 72, 16, 14, 11)
User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (2, 18, 20, 41, 40, 3, 17, 34, 37, 39, 43, 19, 22, 42)

with the first three results being:

[2, 18, 20, 2]
[2]
[41, 2, 40, 18]

Now swap the order of the includes as follows:

u = User.find(2)
u.projects.includes(:pending_contributors, :contributors).each do 
  |p| puts p.contributors.map(&:id).inspect
end

and suddenly the results change (yielding the results I expected) - note the permission load line, it now adds the :pending_contributors condition to that query:

User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 2]]
Project Load (1.1ms)  SELECT "projects".* FROM "projects" INNER JOIN "permissions" ON "projects"."id" = "permissions"."permissionable_id" WHERE "permissions"."user_id" = 2 AND "permissions"."permissionable_type" = 'Project'
Permission Load (1.4ms)  SELECT "permissions".* FROM "permissions" WHERE "permissions"."permissionable_type" = 'Project' AND "permissions"."permissionable_id" IN (64, 56, 54, 53, 51, 50, 61, 62, 63, 57, 65, 66, 48, 49, 67, 68, 69, 70, 71, 60, 72, 16, 14, 11) AND ("permissions"."accepted" = false)
User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (18, 19, 40, 3, 22, 43, 42, 41, 2, 20)

and the results are:

[20]
[]
[41, 40, 18]

If instead of accessing pending_contributors, I access contributors, I get the same issue in reverse. I'm pretty sure this is because both pending_contributors and contributors are using the same source, but I'm not sure why this is an issue.

Is there a way to render all projects and access contributors, and pending_contributors for each project without causing an N+1 query?

Note: I'm on Rails 3.2.

Answers


It looks like you're confusing ActiveRecord by trying to load two sets of overlapping rows from the same table into different associations.

Since the pending_contributors association is included in contributors, I wouldn't make it a separate association, especially if you're going to use them side by side like this.

Instead, I would define pending_contributors as a filter of contributors:

class Project < ActiveRecord::Base
  has_many :permissions, as: :permissionable
  has_many :contributors, through: :permissions, source: :user
  def pending_contributors
    permissions.include(:user).select do |p|
      not p.accepted
    end.map do |p|
      p.user
    end
  end
  ...
end

This isn't perfectly efficient because it means every time you access pending_contributors, it will bring in all contributors from the database, but I think in this case it's a very reasonable tradeoff.

The alternative is some deep-diving in ActiveRecord or having both the association and filter method on the model, which seems a little messy.


Need Your Help

Watir "Element is no longer attached to the DOM" error

javascript ruby selenium-webdriver automated-tests watir-webdriver

I'm having an issue with a test that I've created where I will randomly get this issue, from what I understand it's because there is java script running that is periodically refreshing the element....

Corrupt mercurial repository - cannot update

mercurial corrupt

i think ive managed to corrupt one of mercurial repositories, is there anyway to recover the damage? the message i get when attempting to update the repo to my default branch is: