在实例相关范围的情况下解析 N + 1
Resolve N + 1 in case of instance dependent scope
所以,我遇到了问题
Preloading instance dependent scopes are not supported.
我有三个模型
class P < ApplicationRecord
has_many :as
has_many :cs
end
class C < ApplicationRecord
belongs_to :p
end
class A < ApplicationRecord
belongs_to :p
has_one :c, -> (a) { where(feature: a.feature) }, through: :p, source: :cs
end
我有三个 fast_jsonapi 序列化器
class PSerializer
include FastJsonapi::ObjectSerializer
has_many :as
end
class CSerializer
include FastJsonapi::ObjectSerializer
belongs_to :p
end
class ASerializer
include FastJsonapi::ObjectSerializer
belongs_to :p
has_one :c
end
还有这一个种子文件
p_model = P.create(title: 'PTitle')
4.times do |i|
A.create(title: "aTitle-#{i}", feature: "feature-#{i}", p: p_model)
C.create(title: "cTitle-#{i}", feature: "feature-#{i}", p: p_model)
end
我想用他的A和A的C渲染P
但是当我尝试做
PSerializer.new(P.first, { include: [:as, :'as.c'] }).serialized_json
我得到了
P Load (0.1ms) SELECT "ps".* FROM "ps" ORDER BY "ps"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.1ms) SELECT "as"."id" FROM "as" WHERE "as"."p_id" = ? [["p_id", 1]]
A Load (0.1ms) SELECT "as".* FROM "as" WHERE "as"."p_id" = ? [["p_id", 1]]
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]]
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]]
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]]
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 + 1。但我知道我可以使用包含来解决它。
PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
糟糕:
irb(main):010:0> PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
P Load (0.1ms) 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]]
Traceback (most recent call last):
1: from (irb):10
ArgumentError (The association scope 'c' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)
我可以试试 left_joins
irb(main):011:0> PSerializer.new(P.left_joins({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
Traceback (most recent call last):
1: from (irb):11
ArgumentError (The association scope 'c' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)
其实是一样的。
我该如何解决这个 N+1 问题?
我用这些模型创建了 rails 存储库,所以你可以自己试试。
https://github.com/X1ting/reproduce_preload_bug
Rails 5.2.3
Ruby2.5.1
非常感谢@igor-khodyrev
他建议我使用复合键,而且很管用!
所以,解决方案:
将 gem 添加到 Gemfile
gem 'composite_primary_keys', '=11'
并将 A 模型中的关联更改为此
has_one :c, foreign_key: [:p_id, :feature], primary_key: [:p_id, :feature]
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\"}}}}]}"
我还在我的存储库中添加了解决方案 PR。
https://github.com/X1ting/reproduce_preload_bug/pull/1
所以,我遇到了问题
Preloading instance dependent scopes are not supported.
我有三个模型
class P < ApplicationRecord
has_many :as
has_many :cs
end
class C < ApplicationRecord
belongs_to :p
end
class A < ApplicationRecord
belongs_to :p
has_one :c, -> (a) { where(feature: a.feature) }, through: :p, source: :cs
end
我有三个 fast_jsonapi 序列化器
class PSerializer
include FastJsonapi::ObjectSerializer
has_many :as
end
class CSerializer
include FastJsonapi::ObjectSerializer
belongs_to :p
end
class ASerializer
include FastJsonapi::ObjectSerializer
belongs_to :p
has_one :c
end
还有这一个种子文件
p_model = P.create(title: 'PTitle')
4.times do |i|
A.create(title: "aTitle-#{i}", feature: "feature-#{i}", p: p_model)
C.create(title: "cTitle-#{i}", feature: "feature-#{i}", p: p_model)
end
我想用他的A和A的C渲染P 但是当我尝试做
PSerializer.new(P.first, { include: [:as, :'as.c'] }).serialized_json
我得到了
P Load (0.1ms) SELECT "ps".* FROM "ps" ORDER BY "ps"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.1ms) SELECT "as"."id" FROM "as" WHERE "as"."p_id" = ? [["p_id", 1]]
A Load (0.1ms) SELECT "as".* FROM "as" WHERE "as"."p_id" = ? [["p_id", 1]]
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]]
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]]
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]]
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 + 1。但我知道我可以使用包含来解决它。
PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
糟糕:
irb(main):010:0> PSerializer.new(P.includes({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
P Load (0.1ms) 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]]
Traceback (most recent call last):
1: from (irb):10
ArgumentError (The association scope 'c' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)
我可以试试 left_joins
irb(main):011:0> PSerializer.new(P.left_joins({ as: :c }).first, { include: [:as, :'as.c'] }).serialized_json
Traceback (most recent call last):
1: from (irb):11
ArgumentError (The association scope 'c' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.)
其实是一样的。 我该如何解决这个 N+1 问题?
我用这些模型创建了 rails 存储库,所以你可以自己试试。 https://github.com/X1ting/reproduce_preload_bug Rails 5.2.3 Ruby2.5.1
非常感谢@igor-khodyrev
他建议我使用复合键,而且很管用! 所以,解决方案:
将 gem 添加到 Gemfile
gem 'composite_primary_keys', '=11'
并将 A 模型中的关联更改为此
has_one :c, foreign_key: [:p_id, :feature], primary_key: [:p_id, :feature]
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\"}}}}]}"
我还在我的存储库中添加了解决方案 PR。 https://github.com/X1ting/reproduce_preload_bug/pull/1