Hi,
I am new to Elixir and Phoenix, and I just did the Pragmatic Studio Elixir and Live view courses.
I started a Live view project, and I don’t understand something about a many to many relationship update in my project.
Here is the situation:
I initiated the Authentication module, works just fine.
I created a Context and schema called Groups, in which User can be part of. Each group has a “name”, and can have many users through a join table.
I created a many to many relationship between users and groups, called “members” within the groups.
The creation and deletion of groups work well.
But when I try to update the “name” (string) field of my group, if I only give the “name” field in the params of the changeset, I get an error as errors: [members: {"is invalid", [type: {:array, :map}]} ]
. (full error below)
The only way I managed to make it work so far, is to manually add the existing users to the changeset params:
defp save_group(socket, :edit, %{"name" => _} = group_params) do
case Groups.update_group(
socket.assigns.group,
**Map.put(group_params, "members", socket.assigns.group.members)**
) do
{:ok, group} ->
notify_parent({:saved, group})
{:noreply,
socket
|> put_flash(:info, "Group updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
Logger.error("group editing error")
{:noreply, assign(socket, form: to_form(changeset))}
end
end
I don’t find it convenient because I imagine if I had more fields in my struct, I would also have to pass them manually every time.
Is there a better way to do this?
For better context, here is the error I got initially:
[data:
%Wallet.Groups.Group{
__meta__: #Ecto.Schema.Metadata<:loaded, "groups">,
id: 3, name: "family",
members: [
#Wallet.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
id: 1,
email: "test@gmail.com",
confirmed_at: nil,
inserted_at: ~U[2024-06-29 19:24:02Z],
updated_at: ~U[2024-06-29 19:24:02Z], ...>
],
inserted_at: ~U[2024-06-30 10:34:12Z],
updated_at: ~U[2024-06-30 10:34:12Z]
},
prepare: [],
filters: %{},
params: %{"name" => "Family"},
__struct__: Ecto.Changeset,
required: [],
errors: [
members: {"is invalid", [type: {:array, :map}]}
],
action: nil,
valid?: false,
constraints: [],
types: %{
id: :id,
name: :string,
members: {
:assoc,
%Ecto.Association.ManyToMany{
field: :members,
owner: Wallet.Groups.Group,
related: Wallet.Accounts.User,
owner_key: :id,
queryable: Wallet.Accounts.User,
on_delete: :nothing,
on_replace: :raise,
join_keys: [group_id: :id, user_id: :id],
join_through: "group_members",
on_cast: nil,
where: [],
join_where: [],
defaults: [],
join_defaults: [],
relationship: :child,
cardinality: :many,
unique: false,
ordered: false,
preload_order: []
}
},
inserted_at: :utc_datetime,
updated_at: :utc_datetime
},
repo: nil,
empty_values: [&Ecto.Type.empty_trimmed_string?/1],
validations: [],
changes: %{name: "Family"},
repo_opts: []
]
The Schema:
defmodule Wallet.Groups.Group do
use Ecto.Schema
schema "groups" do
field :name, :string
many_to_many :members, Wallet.Accounts.User, join_through: "group_members"
timestamps(type: :utc_datetime)
end
def changeset(struct, params \\ %{}) do
struct
|> Ecto.Changeset.cast(params, [:name])
|> Ecto.Changeset.put_assoc(:members, Map.get(params, "members"), required: false)
end
end
The code giving me the error:
defp save_group(socket, :edit, group_params) do
case Groups.update_group(socket.assigns.group, group_params) do
{:ok, group} ->
notify_parent({:saved, group})
{:noreply,
socket
|> put_flash(:info, "Group updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
Logger.error("group editing error")
{:noreply, assign(socket, form: to_form(changeset))}
end
end
Finally, the weird map behavior I am getting is the following:
in save_group
function, the group_params
I am receiving are the following: [{"name", "Test"}]
so I thought I could cast this to a Map, that I would then Merge into the existing date, to update the changed values and keep the existing ones, but Elixir doesn’t successfully cast this to a map, even using Map.new, here are my logs with (before on the first line, and after using Map.new):
[debug] [{"name", "Test"}]
[debug] tuple to map
[debug] [{"name", "Test"}]
Although when I try this in a iex session, it works perfectly
Map.new([{"name", "Test"}])
%{"name" => "Test"}
Does anyone have an idea why this doesn’t work in my Phoenix app?