Search code examples
associationselixirecto

Ecto association to more than one schemes


Let's say I have these schemas:

defmodule Sample.Post do
  use Ecto.Schema

  schema "post" do
    field :title
    has_many :comments, Sample.Comment
  end
end

defmodule Sample.User do
  use Ecto.Schema

  schema "user" do
    field :name
    has_many :comments, Sample.Comment
  end
end

defmodule Sample.Comment do
  use Ecto.Schema

  schema "comment" do
    field :text
    belongs_to :post, Sample.Post
    belongs_to :user, Sample.User
  end
end

My questions is how can I use Ecto.build_assoc to save a comment?

iex> post = Repo.get(Post, 13)
%Post{id: 13, title: "Foo"}
iex> comment = Ecto.build_assoc(post, :comments)
%Comment{id: nil, post_id: 13, user_id: nil}

So far it's ok, all I need to do is use the same function to set the user_id in my Comment struct, however since the return value of build_assoc is Comment struct, I can not use the same function

iex> user = Repo.get(User, 1)
%User{id: 1, name: "Bar"}
iex> Ecto.build_assoc(user, :comment, comment)
** (UndefinedFunctionError) undefined function: Sample.Comment.delete/2
...

I have two options but neither of them looks good to me:

First one is to set user_id manually!

iex> comment = %{comment| user_id: user.id}
%Comment{id: nil, post_id: 13, user_id: 1}

Second one is to convert the struct to map and ... I don't even want to go there

Any suggestion?


Solution

  • Why don't you want convert struct to map? It is really easy.

    build_assoc expects map of attributes as last value. Internally it tries to delete key :__meta__. Structs have compile time guarantees, that they will contain all defined fields, so you are getting:

    ** (UndefinedFunctionError) undefined function: Sample.Comment.delete/2
    

    But you can just write:

    comment = Ecto.build_assoc(user, :comment, Map.from_struct comment)
    

    and everything will work fine.