Thib
Thib

Reputation: 193

How to generate multiple nested changesets with cast_assoc

I have Memberships, Lists, Items and Memories defined as follows :

schema "lists" do
  has_many :itemlists, Learnit.Itemlist, on_delete: :delete_all
  many_to_many :items, Learnit.Item, join_through: Learnit.Itemlist
  has_many :memberships, Learnit.Membership, on_delete: :delete_all
end

schema "memberships" do
  belongs_to :list, Learnit.List
  has_many :memorys, Learnit.Memory, on_delete: :delete_all
end

schema "itemlists" do
  belongs_to :item, Learnit.Item
  belongs_to :list, Learnit.List
end

schema "items" do
  has_many :itemlists, Learnit.Itemlist
  many_to_many :lists, Learnit.List, join_through: Learnit.Itemlist
  has_many :memorys, Learnit.Memory
end

schema "memorys" do
  belongs_to :membership, Learnit.Membership
  belongs_to :item, Learnit.Item
end

My Membership's model

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:user_id, :list_id])
  |> validate_required([:user_id, :list_id])
  |> cast_assoc(:memorys)
end

My Memory's model

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:status, :membership_id, :item_id])
  |> foreign_key_constraint([:membership_id, :item_id])
  |> unique_constraint(:membership_id_item_id)
  |> validate_required([:membership_id, :item_id])
end

I try to create a new membership and generate his memories associated with one query. I need to get first Items that are associated with the Membership through its list. Then my plan is to use Membership's cast_assoc property to save the whole in Repo.

How can I load these items in the Membership's changeset? Items are already loaded correctly, I get a Map.put/4 Undefined...

def create(conn, %{"membership" => membership_params}) do
  memories = %{}
  list =
    List
    |> Repo.get!(membership_params["list_id"])
    |> Repo.preload(:items)
    |> Map.get(:items) # Get the list of items
    |> Enum.map(&load_items(&1, memories)) # Loop through the list to get each item
  IO.inspect(memories)
  Map.put(membership_params, :memorys, memories)
  membership_with_memories = Membership.changeset(%Membership{}, membership_params)
  case Repo.insert(membership_with_memories) do
    {:ok, _} ->
      conn
      |> put_flash(:info, "Membership created successfully.")
      |> redirect(to: list_path(conn, :index))
    {:error, membership_with_memories} ->
      Logger.debug("Membership : failed to save membership")
      conn
      |> put_flash(:alert, "Membership was not created.")
      |> redirect(to: topic_path(conn, :index))
  end
end

defp load_items(item, memories) do
  item
  |> Map.put(memories, :item, item) # Add item to the hash
end

Upvotes: 1

Views: 535

Answers (2)

Thib
Thib

Reputation: 193

Just found the problem in my Memory's model. I don't really get why the validate_required blocks the cast_assoc, but it is fine for me...

 def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:status, :membership_id, :item_id])
    #|> foreign_key_constraint([:membership_id, :item_id])  # Dont use that !
    |> unique_constraint(:membership_id_item_id) # Dont forget to put constraint on table too
    |> validate_required([:item_id]) # Cannot use :membership_id with cast_assoc
  end

Upvotes: 0

Thib
Thib

Reputation: 193

I found my way to load the params correctly, but still I cant find the way to save it in repo (I get a "cannot convert the given list to a string").

%{"list_id" => "1", "memorys" => [%{"item_id" => "1"}, %{"item_id" => "2"}, %{"item_id" => "3"}], "user_id" => "1"}

def create(conn, %{"membership" => params}) do
  memories = []
  params =
    List
    |> Repo.get!(params["list_id"])
    |> Repo.preload(:items)
    |> Map.get(:items) # Get the list of items
    |> Enum.map(&add_items(&1, memories)) # Loop through the list to get list of item ids
    |> (&Map.put(params, "memorys", &1)).() # Add the list to membership params
    |> IO.inspect()
  membership_with_memories = Membership.changeset(%Membership{}, params)
  case Repo.insert(membership_with_memories) do
    {:ok, _} ->
      conn
      |> put_flash(:info, "Membership created successfully.")
      |> redirect(to: list_path(conn, :index))
    {:error, membership_with_memories} ->
      Logger.debug("Membership : failed to save membership")
      conn
      |> put_flash(:error, "Membership was not created.")
      |> redirect(to: list_path(conn, :index))
  end
end

defp add_items(item, memories) do
  memories ++ %{"item_id" => Kernel.inspect(item.id)} # Make sure id is a string
end

Upvotes: 1

Related Questions