在实例相关范围的情况下解决 N + 1

Xit*_*ing 5 ruby activerecord ruby-on-rails jsonserializer

所以,我有麻烦

\n\n
Preloading instance dependent scopes are not supported.\n
Run Code Online (Sandbox Code Playgroud)\n\n

我有三个型号

\n\n
Preloading instance dependent scopes are not supported.\n
Run Code Online (Sandbox Code Playgroud)\n\n
class P < ApplicationRecord\n  has_many :as\n  has_many :cs\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n
class C < ApplicationRecord\n  belongs_to :p\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

我有三个 fast_jsonapi 序列化器

\n\n
class A < ApplicationRecord\n  belongs_to :p\n  has_one :c, -> (a) { where(feature: a.feature) }, through: :p, source: :cs\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n
class PSerializer\n  include FastJsonapi::ObjectSerializer\n  has_many :as\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n
class CSerializer\n  include FastJsonapi::ObjectSerializer\n  belongs_to :p\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

还有这个种子文件

\n\n
class ASerializer\n  include FastJsonapi::ObjectSerializer\n  belongs_to :p\n  has_one :c\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

我想用他的 A 和 A\xe2\x80\x99s C\xe2\x80\x99s\n渲染 P 但当我尝试这样做时

\n\n
PSerializer.new(P.first, { include: [:as, :\'as.c\'] }).serialized_json\n
Run Code Online (Sandbox Code Playgroud)\n\n

我有

\n\n
  P Load (0.1ms)  SELECT  "ps".* FROM "ps" ORDER BY "ps"."id" ASC LIMIT ?  [["LIMIT", 1]]\n   (0.1ms)  SELECT "as"."id" FROM "as" WHERE "as"."p_id" = ?  [["p_id", 1]]\n  A Load (0.1ms)  SELECT "as".* FROM "as" WHERE "as"."p_id" = ?  [["p_id", 1]]\n  C Load (0.2ms)  SELECT  "cs".* FROM "cs" INNER JOIN "ps" ON "cs"."p_id" = "ps"."id" WHERE "ps"."id" = ? AND "cs"."feature" = ? LIMIT ?  [["id", 1], ["feature", "feature-0"], ["LIMIT", 1]]\n  C Load (0.1ms)  SELECT  "cs".* FROM "cs" INNER JOIN "ps" ON "cs"."p_id" = "ps"."id" WHERE "ps"."id" = ? AND "cs"."feature" = ? LIMIT ?  [["id", 1], ["feature", "feature-1"], ["LIMIT", 1]]\n  C Load (0.1ms)  SELECT  "cs".* FROM "cs" INNER JOIN "ps" ON "cs"."p_id" = "ps"."id" WHERE "ps"."id" = ? AND "cs"."feature" = ? LIMIT ?  [["id", 1], ["feature", "feature-2"], ["LIMIT", 1]]\n  C Load (0.1ms)  SELECT  "cs".* FROM "cs" INNER JOIN "ps" ON "cs"."p_id" = "ps"."id" WHERE "ps"."id" = ? AND "cs"."feature" = ? LIMIT ?  [["id", 1], ["feature", "feature-3"], ["LIMIT", 1]]\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以,看起来像 N + 1。但我知道我可以使用 include 来解决它。

\n\n
PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :\'as.c\'] }).serialized_json\n
Run Code Online (Sandbox Code Playgroud)\n\n

哎呀:

\n\n
irb(main):010:0> PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :\'as.c\'] }).serialized_json\n  P Load (0.1ms)  SELECT  "ps".* FROM "ps" ORDER BY "ps"."id" ASC LIMIT ?  [["LIMIT", 1]]\n  A Load (0.1ms)  SELECT "as".* FROM "as" WHERE "as"."p_id" = ?  [["p_id", 1]]\nTraceback (most recent call last):\n        1: from (irb):10\nArgumentError (The association scope \'c\' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我可以尝试使用 left_joins

\n\n
irb(main):011:0> PSerializer.new(P.left_joins({ as: :c }).first, { include: [:as, :\'as.c\'] }).serialized_json\nTraceback (most recent call last):\n        1: from (irb):11\nArgumentError (The association scope \'c\' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)\n
Run Code Online (Sandbox Code Playgroud)\n\n

实际上是一样的。\n我该如何解决这个 N + 1 问题?

\n\n

我用这些模型创建了 Rails 存储库,因此您可以自行尝试。\n https://github.com/X1ting/reproduct_preload_bug \nRails 5.2.3\nRuby 2.5.1

\n

Xit*_*ing 4

非常感谢@igor-khodyrev

他建议我使用复合键,效果很好!所以,解决方案:

将 gem 添加到 Gemfile

gem 'composite_primary_keys', '=11'
Run Code Online (Sandbox Code Playgroud)

并将 A 模型中的关联更改为此

  has_one :c, foreign_key: [:p_id, :feature], primary_key: [:p_id, :feature]

Run Code Online (Sandbox Code Playgroud)

N+1 解决:

irb(main):005:0> PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
  P Load (0.2ms)  SELECT  "ps".* FROM "ps" ORDER BY "ps"."id" ASC LIMIT ?  [["LIMIT", 1]]
  A Load (0.1ms)  SELECT "as".* FROM "as" WHERE "as"."p_id" = ?  [["p_id", 1]]
  C Load (0.1ms)  SELECT "cs".* FROM "cs" WHERE ("cs"."p_id" = 1 AND "cs"."feature" = 'feature-0' OR "cs"."p_id" = 1 AND "cs"."feature" = 'feature-1' OR "cs"."p_id" = 1 AND "cs"."feature" = 'feature-2' OR "cs"."p_id" = 1 AND "cs"."feature" = 'feature-3' OR "cs"."p_id" = 1 AND "cs"."feature" = 'feature-kek')
=> "{\"data\":{\"id\":\"1\",\"type\":\"p\",\"relationships\":{\"as\":{\"data\":[{\"id\":\"1\",\"type\":\"a\"},{\"id\":\"2\",\"type\":\"a\"},{\"id\":\"3\",\"type\":\"a\"},{\"id\":\"4\",\"type\":\"a\"},{\"id\":\"5\",\"type\":\"a\"}]}}},\"included\":[{\"id\":\"1\",\"type\":\"a\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}},\"c\":{\"data\":{\"id\":\"1\",\"type\":\"c\"}}}},{\"id\":\"2\",\"type\":\"a\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}},\"c\":{\"data\":{\"id\":\"2\",\"type\":\"c\"}}}},{\"id\":\"3\",\"type\":\"a\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}},\"c\":{\"data\":{\"id\":\"3\",\"type\":\"c\"}}}},{\"id\":\"4\",\"type\":\"a\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}},\"c\":{\"data\":{\"id\":\"4\",\"type\":\"c\"}}}},{\"id\":\"5\",\"type\":\"a\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}},\"c\":{\"data\":null}}},{\"id\":\"1\",\"type\":\"c\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}}}},{\"id\":\"2\",\"type\":\"c\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}}}},{\"id\":\"3\",\"type\":\"c\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}}}},{\"id\":\"4\",\"type\":\"c\",\"relationships\":{\"p\":{\"data\":{\"id\":\"1\",\"type\":\"p\"}}}}]}"
Run Code Online (Sandbox Code Playgroud)

我还将解决方案 PR 添加到了我的存储库中。 https://github.com/X1ting/reproduct_preload_bug/pull/1