Hello, I’m new to Elixir and I’'m strugling with the next problem.
As an admin, I have the power to change roles on other users. Which means I can change their role from member to admin or superuser. The project I’m working in right now uses a many to many relationship between the roles and the users. Now I need to update the role but that is not an actual option in the changeset. So passing this with a put_assoc does not work as I’m getting a “no function clause matching in …”
I have no clue how to achieve it but that could be because of my unexistig knowledge and the code structure so maybe you guys can see how it is setup and get me back on track…
So this is the select option:
<div class="form-group">
<%= label f, :role, class: "control-label" %>
<%= select f, :role, MailgunLogger.Roles.list_roles(), class: "form-control" %>
</div>
When I inspect the params on this update_user function, I get the role param with the ID that matches the role I picked in the selector.
@spec update_user(User.t(), map) :: ecto_user()
def update_user(user, params) do
user
|> User.update_changeset(params)
|> Repo.update()
end
So given the next schema for a user:
schema "users" do
field(:email, :string)
field(:firstname, :string)
field(:lastname, :string)
field(:token, :string)
field(:encrypted_password, :string)
field(:reset_token, :string, default: nil)
field(:password, :string, virtual: true)
many_to_many(:roles, Role, join_through: UserRole, on_replace: :delete)
timestamps()
end
You can see that there is a many to many relation between roles and users => the role of a user cannot be changed directly in the user table but in the roles_user table.
I’m trying to change the role with the put_assoc but it just keeps giving me the no function clause matching error…
@doc false
@spec changeset(User.t(), map()) :: Ecto.Changeset.t()
def changeset(%User{} = user, attrs \\ %{}) do
user
|> cast(attrs, [:firstname, :lastname, :email, :password])
|> validate_required([:email, :password])
|> update_change(:email, &String.downcase/1)
|> validate_format(:email, @email_format)
|> validate_length(:password, min: 8)
|> unique_constraint(:email)
|> hash_password()
|> generate_token()
end
@doc false
@spec update_changeset(User.t(), map()) :: Ecto.Changeset.t()
def update_changeset(%User{} = user, attrs \\ %{}) do
role_id = String.to_integer(attrs["role"])
user
|> cast(attrs, [:firstname, :lastname, :email])
|> update_change(:email, &String.downcase/1)
|> validate_format(:email, @email_format)
|> unique_constraint(:email)
|> put_assoc(:roles, [Roles.get_role_by_id(role_id)])
end
I tried the put_assoc because when no users are registered, you have to create an admin account which gives you a role of admin, what can bee seen here:
@spec admin_changeset(User.t(), map()) :: Ecto.Changeset.t()
def admin_changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :password])
|> unique_constraint(:email)
|> validate_required([:email, :password])
|> update_change(:email, &String.downcase/1)
|> validate_format(:email, @email_format)
|> validate_length(:password, min: 8)
|> hash_password()
|> generate_token()
|> put_assoc(:roles, [Roles.get_role_by_name("admin")]) # Here is the put_assoc
end
So anyone that could get an idea how it could work? Thanks in advance!