Sorebrez
Sorebrez

Reputation: 69

Ecto Changeset - association not casted, if no input is given

I have a User schema

  schema "users" do
    field :name, :string
    field :username, :string
    has_one :user_role, UserRole
  end

  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :username])
    |> validate_required([:name, :username])
    |> unique_constraint(:username)
  end

and the UserRole schema

  schema "userroles" do
    field :is_admin, :boolean, default: false
    belongs_to :user, User
  end

  def changeset(%UserRole{} = user_role, attrs) do
    user_role
    |> cast(attrs, [:is_admin, :user_id])
    |> validate_required([:is_admin])
  end

They are both in the same context "Accounts", where I cast the associations.

      #accounts.ex      
      def create_user(attrs \\ %{}) do
        %User{}
        |> User.changeset(attrs)
        |> Ecto.Changeset.cast_assoc(:user_role, required: true)
        |> Repo.insert()
      end

Here is the UserController's new and create:

  def new(conn, _params) do
    changeset = Accounts.change_user(%User{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

I have a nested form for the User and the UserRole. If I enter all data, everything works as expected. Now, I want to hide the input for UserRole, because I don't want a user to set himself as "is_admin" (but if you are an admin, you should be able to create other admin accounts). So, there's no input for the UserRole anymore if you are a non-admin user. My expectations were that

  1. the default values from the UserRole schema will be inserted, if there is no input or
  2. the validation in UserRole's changeset will say that there is no input, but it is required.

What happened is: The user was created, but the associated user_role is "nil". I don't get why this happens. Can anybody explain this to me?

My solution was: Instead of giving the user a checkbox for the boolean :is_admin, I send a hidden input, setting it to false, but it seems not be the good way.

Upvotes: 1

Views: 223

Answers (1)

Zubair Nabi
Zubair Nabi

Reputation: 1056

You can always use

Map.put_new(map, key, value) in this case

before

case Accounts.create_user(user_params) do

use params = Map.put_new(user_params, :user_role, "anything")

what this will do is, if it finds the :user_role in map, it will use same, if it doesn't find anything, it will use the value you specified.

it needs the value for user_role because there is an association related to it. Though i feel it should be has_many association, but your code shows its has_one, so for every user entry there should be user_role associated to it.

If has_many is to be used, belongs_to should be in user schema, and has_many in user_role schema

Upvotes: 1

Related Questions