1
votes

I will show some related code here, but if you need complete code, you can find it on Github: https://github.com/maple-leaf/phoenix_todo

user_model:

defmodule PhoenixTodo.User do
  use PhoenixTodo.Web, :model

  @derive {Poison.Encoder, only: [:name, :email, :bio, :todos]}

  schema "users" do
    field :name, :string
    field :age, :integer
    field :email, :string
    field :bio, :string

    has_many :todos, PhoenixTodo.Todo

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name])
    |> validate_required([])
  end
end

todo_model:

defmodule PhoenixTodo.Todo do
  use PhoenixTodo.Web, :model

  @derive {Poison.Encoder, only: [:title, :content]}

  schema "todos" do
    field :title, :string
    field :content, :string

    belongs_to :user, PhoenixTodo.User

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [])
    |> validate_required([])
  end
end

user_controller:

defmodule PhoenixTodo.UserController do
    use PhoenixTodo.Web, :controller

    alias PhoenixTodo.User

    def index(conn, _params) do
        json(conn, User |> Repo.all() |> Repo.preload(:todos))
    end

    def show(conn, %{"id" => id}) do
        json(conn, User|> Repo.get(id) |> Repo.preload(:todos))
    end

    def create(conn, data) do
      user = User.changeset(%User{}, data)
      #user = User.changeset(%User{}, %{name: "xxx", todos: [%PhoenixTodo.Todo{}]})
      json conn, user
    end
end

I can get all users or query particular user from :index and :show, but :create will always raise an error about association.

Request with: curl -H "Content-Type: application/json" -X POST -d '{"name": "abc"}' http://localhost:4000/api/users

Error raised like this: cannot encode association :todos from PhoenixTodo.User to JSON because the association was not loaded. Please make sure you have preloaded the association or remove it from the data to be encoded

How to preload :todos when :create? And todos is not a required field when creating user, can we ignore that association?

1
Did you forget to do Repo.insert? Creating a changeset will not insert the user in the database. - Dogbert
@Dogbert I think it's not caused by Repo.insert. But I add that before returning json, and the same error raised. And code of :create now looks like user = User.changeset(%User{}, data) Repo.insert(user) json conn user - tjfdfs
Yes, as you figured out, you should pass the user struct returned by Repo.insert to json, not the changeset. - Dogbert

1 Answers

0
votes

Finally, I make it works.

This turns out that json conn user matters. It seems like json will call Poison to encode the Ecto.Changeset, not a Ecto.Model, which return by changeset. So we should pass a Ecto.Model to json.

And here's how I do it.

user = User.changeset(%User{}, data)
IO.inspect user  # Ecto.Changeset
{:ok, u1} = Repo.insert(user)
IO.inspect u1   # Model!!
json conn, u1 |> Repo.preload(:todos)  # now we can successfully preload :todos