2

has_many を介してポリモーフィックな関連付けを持つモデルを介して接続されているandProjectモデルがあります。Permission テーブルは、保留中のステータスも追跡します。関連するセットアップは次のとおりです。UserPermission

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

すべてのプロジェクトをレンダリングする際に、N+1 クエリの問題を引き起こすことなく、プロジェクトの contributors リストと pending_contributors リストの両方にアクセスしようとしています。includes(:contributors, :pending_contributors)問題は、2 つのインクルードのどちらかが最初に言及されているものだけが機能するため、使用できないことです。

たとえば、私のレールコンソールでは:

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

私のレールコンソールでのこの結果は次のとおりです。

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)

最初の 3 つの結果は次のとおりです。

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

次のように、インクルードの順序を入れ替えます。

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

そして突然結果が変わります(私が期待した結果が得られます)-パーミッションロード行に注意してください。そのクエリに :pending_contributors 条件が追加されます。

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)

結果は次のとおりです。

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

pending_contributors にアクセスする代わりに contributors にアクセスすると、逆に同じ問題が発生します。これは、pending_contributors と contributors の両方が同じソースを使用しているためだと確信していますが、なぜこれが問題なのかはわかりません。

N+1 クエリを発生させずに、すべてのプロジェクトとアクセスの貢献者、および各プロジェクトの pending_contributors をレンダリングする方法はありますか?

注: Rails 3.2 を使用しています。

4

1 に答える 1

0

同じテーブルから異なる関連付けに重複する 2 つの行セットをロードしようとして、ActiveRecord を混乱させているようです。

pending_contributorsアソシエーションは に含まれているためcontributors、特にこのように並べて使用する場合は、別のアソシエーションにはしません。

代わりに、pending_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

にアクセスするたびに、データベースからpending_contributorsすべてを取り込むことになるため、これは完全に効率的ではありませんcontributorsが、この場合は非常に合理的なトレードオフだと思います。

別の方法としては、ActiveRecord を深く掘り下げるか、モデルに関連付けメソッドとフィルター メソッドの両方を用意することですが、これは少し面倒です。

于 2012-06-13T19:20:49.937 に答える