Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ecto remove preload

Tags:

elixir

ecto

Is there any way to do the inverse to preload?

%Post{
  comments: []
}

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

%Post{
  comments: #Ecto.Association.NotLoaded<association :comments is not loaded>,
}
like image 336
lapinkoira Avatar asked Apr 24 '18 08:04

lapinkoira


People also ask

How does ecto preload work?

Preload is a part of Ecto. Query module that provides the Query DSL. Preload is a powerful tool that allows you to avoid N+1 queries from negatively affecting our performance. We must explicitly preload the data that we are accessing for an association between data.

What is ecto query?

Queries are used to retrieve and manipulate data from a repository (see Ecto. Repo ). Ecto queries come in two flavors: keyword-based and macro-based. Most examples will use the keyword-based syntax, the macro one will be explored in later sections.

What is ecto elixir?

Ecto is an official Elixir project providing a database wrapper and integrated query language. With Ecto we're able to create migrations, define schemas, insert and update records, and query them. Changesets. In order to insert, update or delete data from the database, Ecto. Repo.


4 Answers

Ecto.Association.NotLoaded is a plain old simple struct, so you might relatively easy implement this unpreload youself:

defmodule Unpreloader do
  def forget(struct, field, cardinality \\ :one) do
    %{struct | 
      field => %Ecto.Association.NotLoaded{
        __field__: field,
        __owner__: struct.__struct__,
        __cardinality__: cardinality
      }
    }
  end
end

And use it later as:

Unpreloader.forget(%Post{....}, :comments)
like image 127
Aleksei Matiushkin Avatar answered Oct 21 '22 10:10

Aleksei Matiushkin


if you need to compare 2 structs in tests, it's possible to create a comment without preloaded post association by specifying post_id field directly:

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

or else if you don't need comments association in post, just create post and its comments separately:

post = insert!(:post)
comment = insert!(:comment, post_id: post.id)
# instead of
# post = insert!(:post, comments: [build(:comment)])
like image 34
tap349 Avatar answered Oct 21 '22 09:10

tap349


Answering the actual question from 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

If everything else is the same, I'd just delete that field before asserting:

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

or if you want to delete more than one field:

fields = [:comments, :users]
assert Map.drop(post1, fields) == Map.drop(post2, fields)
like image 24
Dogbert Avatar answered Oct 21 '22 11:10

Dogbert


Just wrote a cleaner solution to this today that can dynamically build the %Ecto.NotLoaded{} struct using Ecto's schema reflection:

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
like image 43
Jason Axelson Avatar answered Oct 21 '22 09:10

Jason Axelson