Ecto删除预加载

时间:2018-04-24 08:15:41

标签: elixir ecto

有没有办法反向预加载?

%Post{
  comments: []
}

posts = Repo.all(Post) |> Repo.unload(:comments)

%Post{
  comments: #Ecto.Association.NotLoaded<association :comments is not loaded>,
}

5 个答案:

答案 0 :(得分:2)

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)

答案 1 :(得分:2)

回答评论中的实际问题:

  

问题是我在测试中收到一个已经预加载了关联的对象,我想用一个没有预加载关联的库来测试它,如果只有其中一个有注释我就不能断言post1 == post2预加载

如果其他一切都相同,我会在断言之前删除该字段:

assert Map.delete(post1, :comments) == Map.delete(post2, :comments)

或者如果要删除多个字段:

fields = [:comments, :users]
assert Map.drop(post1, fields) == Map.drop(post2, fields)

答案 2 :(得分:1)

今天只需为此编写一个更干净的解决方案,即可使用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

答案 3 :(得分:0)

这里是处理关联是否已加载的实现。 例如,如果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

答案 4 :(得分:0)

如果您需要在测试中比较2个结构,则可以通过直接指定post字段来创建不带预载post_id关联的注释:

post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# comment = insert!(:comment, post: post)

否则,如果您不需要在帖子中使用comments关联,只需分别创建帖子及其评论:

post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# post = insert!(:post, comments: [build(:comment)])