Adding multiple associations with one cast

Hi!

I’m new to both Ecto and SQL generally, having worked a whole bunch with NodeJS and MongoDB (I’m only 22).

Here’s the relevant parts of my schema:

defmodule Osdi.Person do
  use Ecto.Schema

  schema "people" do
    # other things, more fields

    has_many :taggings, Osdi.Tagging

    # other things, more associations
  end

  def changeset(tagging, params \\ %{}) do
    tagging
    |> Ecto.Changeset.cast_assoc(:person)
    |> Ecto.Changeset.cast_assoc(:tag)
  end
end


defmodule Osdi.Tagging do
  use Ecto.Schema

  schema "taggings" do
    belongs_to :person, Osdi.Person
    belongs_to :tag, Osdi.Tag
  end

  def changeset(person, params \\ %{}) do
    person
    |> Ecto.Changeset.cast_assoc(:taggings)
  end
end

defmodule Osdi.Tag do
  use Ecto.Schema

  schema "tags" do
    has_many :taggings, Osdi.Tagging
  end

  def changeset(tag, params \\ %{}) do
    tag
    |> Ecto.Changeset.cast(params, [:attributes])
  end
end

And then, the equivalent of the following is occuring in my code:

Osdi.Repo.insert(%Osdi.Person{}, %{taggings: [%{created_at: Date, tag: %{name: name}}]})

And I get an error that the person I’m trying to create is missing a bunch of fields, because the person is coming from the person that exists in each taggings person, which does not exist.

What’s the proper way to do this?

If this were mongo, I could create the person, then create all of the tags, and then create all of the taggings with the returned ids, but that’s because I would be doing relationship management manually.

What’s the recommended way to handle creating these nested associations?

Why does your changeset function for Tagging has person as its parameter, and tagging for Person? I think you might have confused these two changesets. I would have written them as

defmodule Osdi.Person do
  use Ecto.Schema
  import Ecto.Changeset

  schema "people" do
    field :name, :stirng
    many_to_many :tags, Osdi.Tag, join_through: Osdi.Tagging
  end

  def changeset(person, %{tags: tags} = params) do
    person
    |> cast(params, [:name])
    |> put_assoc(:tags, tags)
  end
  def changeset(person, params) do
    cast(person, params, [:name])
  end
end
defmodule Osdi.Tagging do
  use Ecto.Schema
  import Ecto.Changeset

  schema "taggings" do
    belongs_to :person, Osdi.Person
    belongs_to :tag, Osdi.Tag
  end

  def changeset(tagging, params) do
    cast(tagging, params, [:person_id, :tag_id])
  end
end
defmodule Osdi.Tag do
  use Ecto.Schema
  import Ecto.Changeset

  schema "tags" do
    field :name, :string
    many_to_many :people, Osdi.Person, join_through: Osdi.Tagging
  end

  def changeset(tag, params) do
    cast(tag, params, [:name])
  end
end
1 Like

Oh yeah, that’s a mistake but it shouldn’t impact anything – I’m actually passing it a tagging so it’s just a name

What’s the point of tagging? Can you connect tags <-> users through a many-to-many table without this extra schema?

I could, but I want created_at on the taggings so I can tell when the tag was added to the person, and created_at on the tag so I can tell when that was created