select relationship

Resource relevant parts
relationships do
belongs_to :debit_account, Budget.Account, allow_nil?: false
belongs_to :credit_account, Budget.Account, allow_nil?: false
end

actions do
defaults [:create, :read, :update, :destroy]
end
relationships do
belongs_to :debit_account, Budget.Account, allow_nil?: false
belongs_to :credit_account, Budget.Account, allow_nil?: false
end

actions do
defaults [:create, :read, :update, :destroy]
end
In live view form_component i have form where i show select to select relationship
# update
assign_new(assigns, :accounts, fn _ -> Budget.Account.list!() |> Enum.map(&{&1.subject, &1.id}) end)}

# Form preperation
AshPhoenix.Form.for_create(Budget.Payment, :create, api: Budget, actor: current_user)

# render
<.input field={{f, :credit_account_id}} type="select" prompt="Select credit account" label="Credit account" options={@accounts} />
<.input field={{f, :debit_account_id}} type="select" prompt="Select debit account" label="Debit account" options={@accounts} />
# update
assign_new(assigns, :accounts, fn _ -> Budget.Account.list!() |> Enum.map(&{&1.subject, &1.id}) end)}

# Form preperation
AshPhoenix.Form.for_create(Budget.Payment, :create, api: Budget, actor: current_user)

# render
<.input field={{f, :credit_account_id}} type="select" prompt="Select credit account" label="Credit account" options={@accounts} />
<.input field={{f, :debit_account_id}} type="select" prompt="Select debit account" label="Debit account" options={@accounts} />
Input component
# Input preperation
def input(%{field: {f, field}} = assigns) do
assigns
|> assign(field: nil)
|> assign_new(:name, fn ->
name = Phoenix.HTML.Form.input_name(f, field)
if assigns.multiple, do: name <> "[]", else: name
end)
|> assign_new(:id, fn -> Phoenix.HTML.Form.input_id(f, field) end)
|> assign(:value, AshPhoenix.Form.value(f, field))
|> assign(:errors, AshPhoenix.Form.errors(f, format: :simple, for_path: [])[field] |> List.wrap)
|> input()
end
# Input preperation
def input(%{field: {f, field}} = assigns) do
assigns
|> assign(field: nil)
|> assign_new(:name, fn ->
name = Phoenix.HTML.Form.input_name(f, field)
if assigns.multiple, do: name <> "[]", else: name
end)
|> assign_new(:id, fn -> Phoenix.HTML.Form.input_id(f, field) end)
|> assign(:value, AshPhoenix.Form.value(f, field))
|> assign(:errors, AshPhoenix.Form.errors(f, format: :simple, for_path: [])[field] |> List.wrap)
|> input()
end
I dont understand why but selects does not hold their selected value. I can select one, but once i select other first looses value. For plain string value AshPhoenix.Form.value(f, field) returns correct input value, but for selects not. Unrelated: for errors i had to do: AshPhoenix.Form.errors(f, format: :simple, for_path: [])[field], because AshPhoenix.Form.errors(f, format: :simple) returned %{[] => %{key => value}} If some relevant info is missing please let me know, i will provide it.
9 Replies
ZachDaniel
ZachDaniel•3y ago
can I see your entire validate callback?
roberts.gulans
roberts.gulansOP•3y ago
# validate callback
@impl true
def handle_event("validate", %{"form" => params}, socket) do
{:noreply, assign(socket, :form, AshPhoenix.Form.validate(socket.assigns.form, params))}
end
# validate callback
@impl true
def handle_event("validate", %{"form" => params}, socket) do
{:noreply, assign(socket, :form, AshPhoenix.Form.validate(socket.assigns.form, params))}
end
ZachDaniel
ZachDaniel•3y ago
🤔 from what I can see so far it looks correct to me. Are there any errors on the form after validating? IO.inspect(AshPhoenix.Form.errors(form, for_path: :all))
roberts.gulans
roberts.gulansOP•3y ago
defmodule Web.Budget.Payments.CreateEditFormComponent do
def render(assigns) do
~H"""
<div>
<.simple_form
:let={f}
for={@form}
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.header><%= @title %></.header>
<.input field={{f, :subject}} type="text" label="Subject" />
<.input field={{f, :amount}} type="number" label="Amount" />
<.input field={{f, :credit_account_id}} type="select" prompt="Select credit account" label="Credit account" options={@accounts} />
<.input field={{f, :debit_account_id}} type="select" prompt="Select debit account" label="Debit account" options={@accounts} />
<:actions>
<.button phx-disable-with="Creating...">Create</.button>
</:actions>
</.simple_form>
</div>
"""
end

def update(%{payment: payment, current_user: current_user} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign(:form, prepare_form(payment, current_user))
|> assign_new(:accounts, fn _ -> Budget.Account.list!() |> Enum.map(&{&1.subject, &1.id}) end)}
end

def handle_event("validate", %{"form" => params}, socket) do
{:noreply, assign(socket, :form, AshPhoenix.Form.validate(socket.assigns.form, params))}
end

def handle_event("save", %{"form" => params}, socket) do
params = Map.put(params, "paid_at", DateTime.utc_now())

case AshPhoenix.Form.submit(socket.assigns.form, params: params) do
{:ok, payment} ->
message =
case socket.assigns.form.type do
:create -> "Payment created successfully"
:update -> "Payment updated successfully"
end

{:noreply,
socket}

{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
end
defmodule Web.Budget.Payments.CreateEditFormComponent do
def render(assigns) do
~H"""
<div>
<.simple_form
:let={f}
for={@form}
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.header><%= @title %></.header>
<.input field={{f, :subject}} type="text" label="Subject" />
<.input field={{f, :amount}} type="number" label="Amount" />
<.input field={{f, :credit_account_id}} type="select" prompt="Select credit account" label="Credit account" options={@accounts} />
<.input field={{f, :debit_account_id}} type="select" prompt="Select debit account" label="Debit account" options={@accounts} />
<:actions>
<.button phx-disable-with="Creating...">Create</.button>
</:actions>
</.simple_form>
</div>
"""
end

def update(%{payment: payment, current_user: current_user} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign(:form, prepare_form(payment, current_user))
|> assign_new(:accounts, fn _ -> Budget.Account.list!() |> Enum.map(&{&1.subject, &1.id}) end)}
end

def handle_event("validate", %{"form" => params}, socket) do
{:noreply, assign(socket, :form, AshPhoenix.Form.validate(socket.assigns.form, params))}
end

def handle_event("save", %{"form" => params}, socket) do
params = Map.put(params, "paid_at", DateTime.utc_now())

case AshPhoenix.Form.submit(socket.assigns.form, params: params) do
{:ok, payment} ->
message =
case socket.assigns.form.type do
:create -> "Payment created successfully"
:update -> "Payment updated successfully"
end

{:noreply,
socket}

{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
end
# remaining parts
defp prepare_form(nil, current_user) do
AshPhoenix.Form.for_create(Budget.Payment, :create, api: Budget, actor: current_user)
end

defp prepare_form(payment, current_user) do
AshPhoenix.Form.for_update(payment, :update, api: Budget, actor: current_user)
end
# remaining parts
defp prepare_form(nil, current_user) do
AshPhoenix.Form.for_create(Budget.Payment, :create, api: Budget, actor: current_user)
end

defp prepare_form(payment, current_user) do
AshPhoenix.Form.for_update(payment, :update, api: Budget, actor: current_user)
end
ZachDaniel
ZachDaniel•3y ago
Any errors?
Are there any errors on the form after validating? IO.inspect(AshPhoenix.Form.errors(form, for_path: :all))
roberts.gulans
roberts.gulansOP•3y ago
%{[] => [paid_at: "is required", amount: "is required"]} I found "solution"
actions do
defaults [:create, :read, :update, :destroy]
end

# to
actions do
defaults [:read, :update, :destroy]

create :create do
argument :debit_account_id, :uuid, allow_nil?: false
argument :credit_account_id, :uuid, allow_nil?: false
end
end
actions do
defaults [:create, :read, :update, :destroy]
end

# to
actions do
defaults [:read, :update, :destroy]

create :create do
argument :debit_account_id, :uuid, allow_nil?: false
argument :credit_account_id, :uuid, allow_nil?: false
end
end
It seems that defaults does not add ability to populate relations. Now IO.inspect(AshPhoenix.Form.errors(form, for_path: :all)) prints
%{
[] => [
paid_at: "is required",
subject: "is required",
credit_account_id: "is required",
debit_account_id: "is required"
]
}
%{
[] => [
paid_at: "is required",
subject: "is required",
credit_account_id: "is required",
debit_account_id: "is required"
]
}
As we can see it now has correct errors for related ids as well. Im not sure if this is intended behaviour of defaults or not. I would argue that defaults [:create, :update], should be able to populate relationship ids as well.
ZachDaniel
ZachDaniel•3y ago
Ah, I see what you mean The arguments won't actually do it either you have to do something with arguments to make them do something but in your case:
relationships do
belongs_to :debit_account, Budget.Account, allow_nil?: false, attribute_writable?: true
belongs_to :credit_account, Budget.Account, allow_nil?: false, attribute_writable?: true
end
relationships do
belongs_to :debit_account, Budget.Account, allow_nil?: false, attribute_writable?: true
belongs_to :credit_account, Budget.Account, allow_nil?: false, attribute_writable?: true
end
thats what you want
roberts.gulans
roberts.gulansOP•3y ago
How to mark as sloved? 😄
ZachDaniel
ZachDaniel•3y ago
You can right click on the channel and tag it with solved but I'll do it 😄

Did you find this page helpful?