Using ecto to insert records into postgress via a channel, how to create the changeset model parameter

I’m using ex_admin to maintain a couple of tables (crud). I am trying to develop some task-based processes https://tech-stories.com/2011/09/15/getting-more-business-value-with-task-based-ui/ now where these tables are maintained. See the picture in this message: https://elixirforum.com/t/please-help-me-persuade-a-java-cto-that-elixir-is-the-right-choice/13067/69. This process is for creating a new user (see picture below), eventually add roles (that are coupled to the user also, this is the next form that appears after pressing the left button), and authorisations (last form).

The formdefinitions are supplied via a channel (json), the forminput is returned via the same channel. I would like to use ecto to insert the records into postgress. The changeset function expects a “model” parameter that looks like this when I IO.inspect it inserting a record via ex_admin:

%Auth.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  email: nil,
  id: nil,
  inserted_at: nil,
  name: nil,
  password: nil,
  password_hash: nil,
  password_repeat: nil,
  roles: #Ecto.Association.NotLoaded<association :roles is not loaded>,
  updated_at: nil,
  usergroup: #Ecto.Association.NotLoaded<association :usergroup is not loaded>,
  usergroup_id: nil
}

My question is how to create this parameter. The schema + changeset functions are here:

defmodule Auth.User do
  use Ecto.Schema
  import Ecto.Changeset
  import Ecto.Query

  schema "users" do
    field :name, :string
    field :email, :string
    field :password, :string, virtual: true
    field :password_repeat, :string, virtual: true
    field :password_hash, :string
    belongs_to :usergroup, Admin.Usergroup
    many_to_many :roles, Admin.Role, join_through: Admin.UserRole, on_replace: :delete

    timestamps()
  end

  def changeset(model, params \\ :invalid) do
    model
    |> cast(params, ~w(name email password usergroup_id))
  end

  def changeset_create(model, params \\ :invalid) do
    model
    |> cast(params, ~w(name email password usergroup_id))
    |> validate_required([:name, :password, :email])
    |> validate_length(:name, max: 20)
    |> unique_constraint(:name)
    |> unique_constraint(:email)
    |> validate_length(:password, min: 8)
    |> validate_format(:email, ~r/@/)
    |> put_pass_hash()
    |> add_roles(params)
  end

  def changeset_update(model, params \\ :invalid) do
    model
    |> cast(params, ~w(name email))
    |> validate_required([:name, :email])
    |> validate_format(:email, ~r/@/)
    |> validate_length(:name, max: 20)
    |> unique_constraint(:name)
    |> unique_constraint(:email)
    |> add_roles(params)
  end

  def add_roles(changeset, params) do
  # see https://github.com/smpallen99/ex_admin, code had to be customized
    if Map.get(params, :role_ids, []) !== [""] do
      ids = params.role_ids |> Map.keys() |> Enum.map(fn atom -> atom |> Atom.to_string() |> String.to_integer() end)
      roles = Admin.Repo.all(from r in Admin.Role, where: r.id in ^ids)
      put_assoc(changeset, :roles, roles)
    else
      put_assoc(changeset, :roles, [])
    end
  end

  def put_pass_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
        put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))
      _ ->
        changeset
    end
  end
end

I’m not sure I understand the question.

That parameter is simply:

%Auth.User{}

and to create a new user changeset:

changeset = Auth.User.changeset_create(%Auth.User{}, params)

where params is a map with at least the string keys "name", "email", "password", "usergroup_id" with values extracted from your form data.

Typically there is only one changeset/2 function, so you have to supply %Auth.User{} as the struct when you are creating a new user. What is strange here is that there are three separate versions of the changeset function. changeset_update/2 doesn’t seem to process password updates (doesn’t generate a hash) and changeset doesn’t do any validation, nor process roles.

3 Likes

Nice, thanks. A dummy question, sorry. :slight_smile: