Adding many_to_many assoc on insert

New to Elixir…this is my first project.

I have a Event ecto model (events) that has many_to_many users, so a events_users table.

This is what I have attempted to far

def create(conn, %{"event" => event_params}) do
		changeset = Event.create_changeset(%Event{owner_id: LivePaper.Authentication.current_user(conn).id}, event_params)

		case Repo.insert(changeset) do
			{:ok, event} ->
				Ecto.Changeset.cast_assoc(event, :users, [LivePaper.Authentication.current_user(conn)])

				conn
				|> put_flash(:info, "Event created")
				|> redirect(to: events_path(conn, :show, event.id))
			{:error} ->
				render conn, "new.html", changeset: changeset
		end
	end

With this model

defmodule LivePaper.Event do
	use LivePaper.Web, :model

	schema "events" do
		field :name, :string
		field :link, :string
		field :owner_id, :integer
		many_to_many :users, LivePaper.User, join_through: "events_users"

		timestamps()
	end

  @required_fields ~w(name)
  @optional_fields ~w()

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

  def create_changeset(model, params \\ %{}) do
  	model
  	|> cast(params, @required_fields, @optional_fields)
  	|> put_change(:link, create_link(params["name"]))
    |> unique_constraint(:link)
  end

  defp create_link(name) do
  	name
  end
end

I know I am probably doing a lot of other things wrong, if you want to chime in, that’s fine. But, the event gets created and then I tried to add the user to that event and get this error no function clause matching in Ecto.Changeset.cast_relation/4

The event gets created just fine, but that line Ecto.Changeset.cast_assoc(event, :users, [LivePaper.Authentication.current_user(conn)]) is failing. Maybe that’s not the right way to do it, I don’t know. Ive spent hours googling trying to figure it out. Any help or assistance is appreciated.

1 Like

For now I just created a model for UserEvent and inserted it after the Event got inserted, it just feels wrong.

Repo.insert(EventUser.changeset(%EventUser{event_id: event.id, user_id: LivePaper.Authentication.current_user(conn).id}))

Why does it feel wrong? I mean it makes your code readable and next developer who will look at it will immediately know what’s going on. I would prefer this way of doing things. I mean, if you were to write SQL, what would you do? Create event, then create association, and possibly wrap it all into transaction. I’d go with doing just that in your code, possibly with the use of Ecto.Multi.

But if you really do want to fix your code and use facilities provided by Ecto, you need to fix your code. What you have wrong is that you first insert the changeset into repo, and then cast_assoc, which updates the changeset - but then you do nothing with it.

The correct way is to first assemble a changeset that consists of your Event and [User] in association, and then insert it. You can do it by moving your cast_assoc to your create_changeset function. Check out this article by @josevalim for detailed explanation:

http://blog.plataformatec.com.br/2015/08/working-with-ecto-associations-and-embeds/

and relevant code to amend your create_changeset function is:

def changeset(todo_list, params \\ :empty) do
  todo_list
  |> cast(params, @required_fields, @optional_fields)
  |> cast_assoc(:todo_items, required: true)
end