Ecto 去除预紧力
Ecto remove preload
有什么方法可以做相反的预加载吗?
%Post{
comments: []
}
posts = Repo.all(Post) |> Repo.unload(:comments)
%Post{
comments: #Ecto.Association.NotLoaded<association :comments is not loaded>,
}
Ecto.Association.NotLoaded
是一个普通的老式简单结构,因此您自己实现这个 unpreload
可能相对容易:
defmodule Unpreloader do
def forget(struct, field, cardinality \ :one) do
%{struct |
field => %Ecto.Association.NotLoaded{
__field__: field,
__owner__: struct.__struct__,
__cardinality__: cardinality
}
}
end
end
稍后用作:
Unpreloader.forget(%Post{....}, :comments)
根据评论回答实际问题:
The issue is I am receiving in a test an object which already has preloaded an association and I want to test it with a library which isnt preloading the association and I cannot assert post1 == post2 if just one of them has the comments preloaded
如果其他一切都一样,我会在断言之前删除该字段:
assert Map.delete(post1, :comments) == Map.delete(post2, :comments)
或者如果您要删除多个字段:
fields = [:comments, :users]
assert Map.drop(post1, fields) == Map.drop(post2, fields)
今天刚刚为此编写了一个更简洁的解决方案,可以使用 Ecto's schema reflection:
动态构建 %Ecto.NotLoaded{}
结构
defmodule UnPreloader do
def clear_associations(%{__struct__: struct} = schema) do
struct.__schema__(:associations)
|> Enum.reduce(schema, fn association, schema ->
%{schema | association => build_not_loaded(struct, association)}
end)
end
defp build_not_loaded(struct, association) do
%{
cardinality: cardinality,
field: field,
owner: owner,
} = struct.__schema__(:association, association)
%Ecto.Association.NotLoaded{
__cardinality__: cardinality,
__field__: field,
__owner__: owner,
}
end
end
这里是处理关联是否加载的实现。
例如,如果 Post 有用户和评论
result = Post |> preload(:comments)
UnPreloader.clear_associations(result)
输出将预加载评论并删除用户
实施:
defmodule UnPreloader do
require Logger
@doc """
When list is passed as parameter it will match call this function
"""
def clear_associations(list) when is_list(list) do
Enum.map(
list,
fn item -> clear_associations(item)
end
)
end
@doc """
When struct is passed as parameter it will match call this function.
We fetch all associations in struct and then call map_schema which will check if association is not loaded
"""
def clear_associations(%{__struct__: struct} = schema) do
associations = struct.__schema__(:associations)
map_schema(schema, associations)
end
@doc """
When nil is passed as parameter it will match call this function.
"""
def clear_associations(nil = schema) do
nil
end
@doc """
When we call multiple associations this function is called and it replaces each association in schema with eather
warning or actual data, depends if association is loaded.
"""
defp map_schema(schema, associations) when length(associations) > 0 do
associations
|> Enum.reduce(
schema,
fn association, schema ->
%{schema | association => map_assoc_data(Map.get(schema, association))}
end
)
end
@doc """
If schema has 0 associations we dont need to do anything. aka recursion braker
"""
defp map_schema(schema, associations) when length(associations) == 0 do
schema
end
@doc """
If schema is nil we just return nil
"""
defp map_assoc_data(data) when data == nil do
nil
end
@doc """
If schema is actually our produced warning we will just return it back
"""
defp map_assoc_data(%{warning: _} = data) do
data
end
@doc """
If schema is actually a list we want to clear each single item
"""
defp map_assoc_data(associationData) when is_list(associationData) do
Enum.map(
associationData,
fn data ->
clear_associations(data)
end
)
end
@doc """
If schema is not list and association is not loaded we will return warning
"""
defp map_assoc_data(%{__struct__: struct} = schema)
when struct == Ecto.Association.NotLoaded and is_list(schema) == false do
Logger.warn("Warning data not preloaded #{inspect schema}")
%{
warning: "DATA NOT PRELOADED"
}
end
@doc """
If schema is not list and association is loaded we will go deeper into schema to search for associations inside
which are not loaded
"""
defp map_assoc_data(%{__struct__: struct} = schema)
when struct != Ecto.Association.NotLoaded and is_list(schema) == false do
clear_associations(schema)
end
end
如果您需要在测试中比较 2 个结构,可以通过直接指定 post_id
字段来创建没有预加载 post
关联的评论:
post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# comment = insert!(:comment, post: post)
否则,如果您不需要 post 中的 comments
关联,只需单独创建 post 及其评论:
post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# post = insert!(:post, comments: [build(:comment)])
有什么方法可以做相反的预加载吗?
%Post{
comments: []
}
posts = Repo.all(Post) |> Repo.unload(:comments)
%Post{
comments: #Ecto.Association.NotLoaded<association :comments is not loaded>,
}
Ecto.Association.NotLoaded
是一个普通的老式简单结构,因此您自己实现这个 unpreload
可能相对容易:
defmodule Unpreloader do
def forget(struct, field, cardinality \ :one) do
%{struct |
field => %Ecto.Association.NotLoaded{
__field__: field,
__owner__: struct.__struct__,
__cardinality__: cardinality
}
}
end
end
稍后用作:
Unpreloader.forget(%Post{....}, :comments)
根据评论回答实际问题:
The issue is I am receiving in a test an object which already has preloaded an association and I want to test it with a library which isnt preloading the association and I cannot assert post1 == post2 if just one of them has the comments preloaded
如果其他一切都一样,我会在断言之前删除该字段:
assert Map.delete(post1, :comments) == Map.delete(post2, :comments)
或者如果您要删除多个字段:
fields = [:comments, :users]
assert Map.drop(post1, fields) == Map.drop(post2, fields)
今天刚刚为此编写了一个更简洁的解决方案,可以使用 Ecto's schema reflection:
动态构建%Ecto.NotLoaded{}
结构
defmodule UnPreloader do
def clear_associations(%{__struct__: struct} = schema) do
struct.__schema__(:associations)
|> Enum.reduce(schema, fn association, schema ->
%{schema | association => build_not_loaded(struct, association)}
end)
end
defp build_not_loaded(struct, association) do
%{
cardinality: cardinality,
field: field,
owner: owner,
} = struct.__schema__(:association, association)
%Ecto.Association.NotLoaded{
__cardinality__: cardinality,
__field__: field,
__owner__: owner,
}
end
end
这里是处理关联是否加载的实现。 例如,如果 Post 有用户和评论
result = Post |> preload(:comments)
UnPreloader.clear_associations(result)
输出将预加载评论并删除用户
实施:
defmodule UnPreloader do
require Logger
@doc """
When list is passed as parameter it will match call this function
"""
def clear_associations(list) when is_list(list) do
Enum.map(
list,
fn item -> clear_associations(item)
end
)
end
@doc """
When struct is passed as parameter it will match call this function.
We fetch all associations in struct and then call map_schema which will check if association is not loaded
"""
def clear_associations(%{__struct__: struct} = schema) do
associations = struct.__schema__(:associations)
map_schema(schema, associations)
end
@doc """
When nil is passed as parameter it will match call this function.
"""
def clear_associations(nil = schema) do
nil
end
@doc """
When we call multiple associations this function is called and it replaces each association in schema with eather
warning or actual data, depends if association is loaded.
"""
defp map_schema(schema, associations) when length(associations) > 0 do
associations
|> Enum.reduce(
schema,
fn association, schema ->
%{schema | association => map_assoc_data(Map.get(schema, association))}
end
)
end
@doc """
If schema has 0 associations we dont need to do anything. aka recursion braker
"""
defp map_schema(schema, associations) when length(associations) == 0 do
schema
end
@doc """
If schema is nil we just return nil
"""
defp map_assoc_data(data) when data == nil do
nil
end
@doc """
If schema is actually our produced warning we will just return it back
"""
defp map_assoc_data(%{warning: _} = data) do
data
end
@doc """
If schema is actually a list we want to clear each single item
"""
defp map_assoc_data(associationData) when is_list(associationData) do
Enum.map(
associationData,
fn data ->
clear_associations(data)
end
)
end
@doc """
If schema is not list and association is not loaded we will return warning
"""
defp map_assoc_data(%{__struct__: struct} = schema)
when struct == Ecto.Association.NotLoaded and is_list(schema) == false do
Logger.warn("Warning data not preloaded #{inspect schema}")
%{
warning: "DATA NOT PRELOADED"
}
end
@doc """
If schema is not list and association is loaded we will go deeper into schema to search for associations inside
which are not loaded
"""
defp map_assoc_data(%{__struct__: struct} = schema)
when struct != Ecto.Association.NotLoaded and is_list(schema) == false do
clear_associations(schema)
end
end
如果您需要在测试中比较 2 个结构,可以通过直接指定 post_id
字段来创建没有预加载 post
关联的评论:
post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# comment = insert!(:comment, post: post)
否则,如果您不需要 post 中的 comments
关联,只需单独创建 post 及其评论:
post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# post = insert!(:post, comments: [build(:comment)])