Fetching items with unnecessary info - can I avoid fetching user data?

This is really fast question only for my information if it is normal, or for getting some tip how to avoid if its really bad move.

I write tests for my Book API and while testing get_book/list_books etc. Assert fails because my goal was’nt to preload user while fetching Book from db. It fetches whole informations about associated user. I think user_id is enough to get for example his email, or put link to a book creator account.

I post whole road to create and get book and then my test.

Controller

  def create(conn, %{"book" => book_params}) do
    user = conn.assigns.current_user

    with {:ok, %Book{} = _book} <- Books.create_book(user, book_params) do
      conn
      |> put_status(:created)
      |> json(%{message: "created"})
    end
  end

Context

  def create_book(user, %{"authors" => _authors, "categories" => _categories} = attrs) do
    %Book{}
    |> Book.changeset(Map.drop(attrs, ["authors", "categories"]))
    |> Ecto.Changeset.put_assoc(:user, user)
    |> load_authors_assoc(attrs)
    |> load_categories_assoc(attrs)
    |> Repo.insert()
  end

Context get_book/1
def get_book(id), do: Repo.get(Book, id) |> Repo.preload([:authors, :categories])

Test

    @valid_attrs %{
                    "title" => "Book 1",
                    "description" => "Book description",
                    "isbn" => "111-111-111",
                    "published" => 2000,
                    "authors" => "author",
                    "categories" => "category"
                  }
    setup do
      %{user: AccountsFixtures.user_fixture()}
    end

    test "get_book/1 returns the book with given id", %{user: user} do
      book = BooksFixtures.book_fixture(user, @valid_attrs)
      assert Books.get_book(book.id) == book
    end

Test fails because left side dont load the user info, just his id. Right side basically show whole user what I think is not too good.

user: #Ecto.Association.NotLoaded<association :user is not loaded>,

Versus:

user: %Bookshare.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "user-576460752303423165@example.com", hash_password: "$2b$04$oyDM7WvxEIOTprM2Gb/f8uobuAeFKYbxwxST8I50iMR8vmjFRLsoe", id: 2240, inserted_at: ~N[2022-11-13 19:47:36], is_confirmed: false, password: nil, profile: #Ecto.Association.NotLoaded<association :profile is not loaded>, updated_at: ~N[2022-11-13 19:47:36]},

I know that to pass test I have to change get_book function to:
def get_book(id), do: Repo.get(Book, id) |> Repo.preload([:user, :authors, :categories])

But I would like to know if I can avoid fetching user data. I suppose it happens because I put into create_book function current user and then make association in changeset. Maybe instead of putting user to function I should put just user_id which contains enought information about owner and not too much for viewers.

The only thing about relationship written in book.ex and user.ex is
belongs_to :user, Bookshare.Accounts.User, foreign_key: :user_id

book has its user populated because it was built using put_assoc.

If you don’t normally expect to use user on the result from Books.get_book, consider breaking the whole-struct == assertion apart into specific assertions about which parts you do expect to use.

3 Likes

As always very helpful! Thanks for this :slight_smile: