Coder Social home page Coder Social logo

ash_phoenix's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ash_phoenix's Issues

Form refactor: validate_and_submit() helper

Minor DX improvement? I'm going to be doing a lot of this:

  def handle_event("submit", %{"form" => params}, socket) do
    form = AshPhoenix.Form.validate(socket.assigns.form, params)
    case AshPhoenix.Form.submit(form, Crm.Accounts) do
      {:ok, _} ->
      ...
    end
  end

when i could:

  def handle_event("submit", %{"form" => params}, socket) do
    case AshPhoenix.Form.validate_and_submit(socket.assigns.form, params, Crm.Accounts) do
      {:ok, _} ->
      ...
    end
  end

I can't think of a reason why I'd need to do something with the validated form before passing to submit? But if i do, i still can. Unless I'm doing it wrong in the first place? Of course i could implement my own helper

UndefinedFunctionError when destroying a resource via ash_admin

Describe the bug
I noticed it while testing stuff in ash_admin but I believe the root cause is here.

When destroying a record via ash_admin, the following error is raised:

** (UndefinedFunctionError) function :ok.resource/0 is undefined (module :ok is not available)
    :ok.resource()
    (ash 1.52.0-rc.8) lib/ash/api/api.ex:987: Ash.Api.update/3
    (my_app 0.1.0) lib/my_app/ash/api.ex:1: MyApp.Api.update/2
    (ash_phoenix 0.7.2-rc.0) lib/ash_phoenix/form/form.ex:1160: AshPhoenix.Form.with_changeset/2
    (ash_phoenix 0.7.2-rc.0) lib/ash_phoenix/form/form.ex:1076: AshPhoenix.Form.submit/2
    (ash_admin 0.4.4) lib/ash_admin/components/resource/form.ex:1083: AshAdmin.Components.Resource.Form.handle_event/3

Looking at that line of ash_phoenix, it looks like it's trying to call an update function after a destroy? Which was introduced as part of this commit:

50f6a10#diff-9cf68247dcbdd870091032f2ed3c7f1ac6ee87d1dfb2051658c4e61cab2fe3efR1074

Should this line have been added?

To Reproduce

I'm not sure how to trigger it outside ash_admin. But within ash_admin, triggering a Destroy action will cause it.

Expected behavior

The action does actually delete the record, but the LV crashes and so the app is left on a white screen - this shouldn't happen, it should go back to the default read action or schema or something with a flash message. Whatever it did before the bug was introduced!

** Runtime

  • Elixir version - 1.13.3
  • Erlang version - 24.3
  • OS - macOS Monterey
  • Ash/AshPhoenix/AshAdmin version - current main

Form refactor: improve error message when wrong API provided to submit

Minor DX improvement, when providing the wrong API to submit, currently getting:

** (BadMapError) expected a map, got: nil
    (ash_phoenix 0.4.23-rc.1) lib/ash_phoenix/form/form.ex:698: AshPhoenix.Form.submit/3
    (crm 0.1.0) lib/crm_web/live/profile_live.ex:83: CrmWeb.ProfileLive.handle_event/3

An ability to drop some values from the form struct

As per Discord discussion:

Is your feature request related to a problem? Please describe.

I've noticed that AshPhoenix.Form persists data if there were errors, which makes sense, but sometimes it could be sensitive data like passwords. I'd like to scrub them from the form struct.

Describe the solution you'd like
Perhaps drop values which marked as sensetive?: true.

Or maybe add clear_value(form, list_of_fields_to_clear) function for this.

Describe alternatives you've considered
Drop those values manually from the form struct.

AshPhoenix.Form needs some way of maintaining order of nested records

Is your feature request related to a problem? Please describe.

As discussed earlier. If we -

  • show a form with nested data, eg. Element with many ToolSpecifications
  • save the form successfully, which returns the updated Element
  • reload the Element + nested data
  • rebuild the form with the reloaded data

The nested records might not be in the same order as on the original form, thus re-rendering the page would have the nested records re-ordering and shifting on the page.

Even if we sort forms in the view, with something like

Enum.sort_by(inputs_for(@form, :tool_specifications), & Form.value(&1, :inserted_at))

Nested records may have been created at the same time, so the inserted_at timestamps will be the same and thus fall back to the database default ordering. Correct sorting of timestamps also needs an extra argument like {:asc, DateTime} to be passed to Enum.sort_by/3, which will then fail when new nested records are built (because the timestamps will be not be set).

And if a field in the nested form is focussed at the time of save - Phoenix won't change the value of any focussed field, even though the value underlying it may change. So eg.

  • editing Quantity of nested tool specification 1
  • save form with Quantity field still in focus
  • Tool specification 1 is now end of the list of loaded tool specifications
  • First nested form still shows with the entered Quantity even though the underlying record has changed, so it looks like two records now have the data when you only updated one.

(That may be more of an edge case for my project as we have keyboard shortcuts for saving, instead of a submit button)

Describe the solution you'd like

A way to take a new form, and an old form, and tell the new form to use the order of nested records from the old form.

Express the feature either with a change to resource syntax, or with a change to the resource interface

Possibly something like:

Form.for_update(element, :update, api: MyApp.Api, forms: [auto?: true]
|> Form.reorder_nested(previous_form)

That would then iterate over the nested data and make sure ordering in the new form matches the old form.

Write a LiveView specific tutorial

Its been pointed out that we don't have a "Getting Started with LiveView", and given that most (or many) of our users are using LiveView this would likely be a very helpful piece of material :)

Phoenix 1.7 version?

hey,

very interesting project

is there a phx 1.7 branch I can PoC against for AshPhoenix

I've upgraded my own project to phoenix 1.7rc0 (which has a bunch of new stuff)

Error on submit when removing the last item from a nested resource

See test case in #20

C&P from discord:

whenever we remove a form, we just track the path we're removing e.g

%Form{
  removed_paths: [
    [:comments, 0, :post],
    [:post, :comments, 0]
  ]
}

and then when we create a form we just traverse all the forms and their data and remove those things
Yeah, thats what we'll have to do

dblack — Today at 16:19

uh, say i remove a form, then add a form, then remove a form... will that all fall over as the new form might get blown away on validate by the list of removed_paths.

ZachDaniel — Today at 16:19

No, that should be fine

dblack — Today at 16:20

cool

ZachDaniel — Today at 16:20

because when we handle these removed_paths, we're only going to remove the data that backs them
like if you had data like post: [comment, comment, comment]
if you remove [:post, :comments, 0] we also get the data for that form, and remove the 0th thing
and thats effectively "permanent"

Syntax error in ash_phoenix.gen.live generated code.

Describe the bug
I'm creating a toy application for tracking weight lifting workouts. I have a WeightType resource that I want to create a LiveView for. When running the Ash Phoenix LiveView generator I'm getting the following error and having trouble tracking down where it comes from so I can fix it. Help?

mix ash_phoenix.gen.live Mitochex.Workouts Mitochex.Workouts.WeightType
Would you like to name your actor? For example: `current_user`. If you choose no, we will not add any actor logic. [Yn] n
Please provide a plural_name. For example the plural of tweet is tweets.
You can press enter to abort, and then configure one on the resource, for example:

    resource do
      plural_name "tweets"
    end
> weight_types
** (SyntaxError) lib/mitochex_web/live/weight_type_live/index.ex:80:87: unexpected token: }

    HINT: the "(" on line 80 is missing terminator ")"

    |
 80 |       |> assign(:weight_type, Mitochex.Workouts.get!(Mitochex.Workouts.WeightType, id)})
    |                                                                                       ^
    (elixir 1.15.5) lib/code.ex:945: Code.format_string!/2
    (mix 1.15.5) lib/mix/tasks/format.ex:600: Mix.Tasks.Format.elixir_format/2
    (ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:162: AshPhoenix.Gen.Live.write_formatted_template/5
    (ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:97: AshPhoenix.Gen.Live.generate/3
    (mix 1.15.5) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.15.5) lib/mix/cli.ex:92: Mix.CLI.run_task/2
    /Users/cro/.local/share/rtx/installs/elixir/1.15.5-otp-26/bin/mix:2: (file)

To Reproduce

mix ash_phoenix.gen.live against an existing resource

Expected behavior

Generator should produce code without the syntax error.

** Runtime

  • Elixir 1.15.5
  • Erlang 26.0.2
  • macOS 13.5.2
  • Ash version
      {:ash, "~> 2.14"},
      {:ash_postgres, "~> 1.3.6"},
      {:ash_phoenix, "~> 1.2"},
      {:ash_authentication, "~> 3.11"},
      {:ash_authentication_phoenix, "~> 1.7"}

Invalid select input for atom attributes with one_of constraint

Describe the bug
The generator creates a multiple select input for atom attributes with the constraint one_of

To Reproduce
When you have an attribute like:

attribute :status, :atom do
  constraints one_of: [:available, :reserved, :sold]
end

The generator mix ash_phoenix.gen.live Api Resource will generate a select input with the attribute multiple set which will make the field validation fail.

Expected behavior
I expect the generator to generate a select input without the attribute multiple set.

Runtime

  • Elixir version 1.15.7
  • Erlang version 26.1.2
  • OS Ubuntu
  • Ash version 2.16.1
  • ash_authentication_phoenix 1.8.7

Additional context
none

Documentation Example Error

This example appears on ash-hq:

form = AshPhoenix.Form.for_update(post,
api: MyApp.MyApi,
forms: [
comments: [
resource: Comment,
data: post.comments,
create_action: :create,
update_action: :update
forms: [
sub_comments: [
resource: Comment,
data: &(&1.sub_comments),
create_action: :create,
update_action: :update
]
]
]
])

Note that the action parameter is missing. Is it supposed to be?

validate_opts on `add_form` and `remove_form`

Describe the bug
Thanks for adding the validate_opts to these two functions. They're not working though!

To Reproduce
Setting validate_opts like:

form = AshPhoenix.Form.add_form(form, :nested, validate_opts: [errors: false])

is hiding errors on the outer form, but not for the :nested form

Expected behavior
both the outer and nested forms to have errors hidden/not populated on the form, when setting validate_opts: [errors: false]

** Runtime

  • Elixir version
  • Erlang version
  • OS
  • Ash version: main
  • any related extension versions

Additional context
Add any other context about the problem here.

mix ash_phoenix.gen.live doesn't add fields to show.ex

Describe the bug
mix ash_phoenix.gen.live doesn't add fields to show.ex, but they are added to form_component.ex and index.ex

To Reproduce
mix ash_phoenix.gen.live AshActivityPlanner.Planner AshActivityPlanner.Planner.Participant

in

https://github.com/jarlah/ash-activity-planner

(which will ofc now overwrite the existing live view

Expected behavior
Fields should also be added to show.ex, as I have modified it after running the generator in https://github.com/jarlah/ash-activity-planner/blob/main/lib/ash_activity_planner_web/live/participant_live/show.ex#L18

Additional context

Forum discussion:

https://elixirforum.com/t/how-to-convert-mix-gen-live-to-ash-compatible-live-view/59785

Latest version `1.3.2` of Ash Phoenix depends on Ash version `~> 2.16.1` with known bug

Describe the bug

Required version of latest ash phoenix release 1.3.2 requires ash version with (upstream fixed) bug that ensures that "If the aggregated resource has a default preparation that will load other resources, the aggregation will fail". See Ash issue #806

To Reproduce
Have following in mix.exs's deps, and try to load multiple aggregates, follow Ash issue #806 to reproduce.

{:ash, "~> 2.16.1"},
{:ash_phoenix, "~> 1.3.2"},

Expected behavior
A clear and concise description of what you expected to happen.

Runtime

  • Elixir version 1.15.7
  • Erlang version 25
  • OS darwin
  • Ash version 2.16.1
  • any related extension versions phoenix 1.3.2

Rename `FilterForm.to_filter!`

FilterForm.to_filter was renamed some time ago to to_filter_expression but it's raising sibling to_filter! didn't get renamed at the same time. Can I rename to_filter! to to_filter_expression! and deprecate to_filter with a message saying use to_filter_expression!?

AshPhoenix.Form.submit doesn't apply `change` from action.

Describe the bug
Submitting a Form by AshPhoenix.Form.submit doesn't apply a change from defined action. The change actions sets the tenant automatically. Committing the change using the Ash.Changeset |> MyApp.Api.create() and passing params from form works. I have to pass the tenant explicitly for the AshPhoenix.Form.submit to work.

To Reproduce
I have a resource with roughly that structure:

defmodule MyApp.Api.SomeResource do
  use MyApp.Api.SomeResource

  actions do
    create :create do
      primary? true

      change MyApp.Api.Changes.SetTenantFromActor
    end
  end

  attributes do
    ...attributes
  end

  multitenancy do
    strategy :attribute
    attribute :organization_id
  end

  identities do
    identity :organization_some_resource, :name
  end

  policies do
    policy action(:create) do
      forbid_unless MyApp.Api.Checks.IsAllowedTenant
      ...some other checks
    end
  end
end

With SetTenantFromActor being:

defmodule MyApp.Api.Changes.SetTenantFromActor do
  @moduledoc false
  use Ash.Resource.Change

  @impl true
  def change(%Ash.Changeset{tenant: nil} = changeset, _opts, %{
        actor: %Actor{metadata: %{organization_id: organization_id}}
      }) do
    Ash.Changeset.set_tenant(changeset, organization_id)
  end

  def change(changeset, _opts, _context), do: changeset
end

I have a AshPhoenix.Form created using AshPhoenix.Form.for_create(MyApp.Api.SomeResource, :create, api: MyApp.Api) and the following code handling the form changes/submit.

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

    {:noreply, assign(socket, :form, form)}
  end

  def handle_event("submit", %{"form" => params}, socket) do
    case AshPhoenix.Form.submit(socket.assigns.form,
           params: params,
           api_opts: [
             actor: socket.assigns.actor,
           ]
         ) do
      {:ok, _resource} ->
        {:noreply, socket}

      {:error, _error} ->
        {:noreply, put_flash(socket, :error, "Error")}
    end
  end

Submitting the form returns the {:error, form} tuple, with form having no errors, but the errors? attribute being set to true.
Passing the tenant explicitly to the api_opts of AshPhoenix.Form.submit fixes the issue.

Expected behavior
The AshPhoenix.Form.submit should apply a change from create action.

** Runtime

  • Elixir version - 1.14.1
  • Erlang version - 25.1.2
  • OS - macOS 12.5.1
  • Ash version - 2.5.15
  • Ash.Phoenix version - 1.2.5

Use resource defined Api if opts[:api] not set

Proposal

Allow to avoid the :api option on form creation if an Api is define on the resource.

Describe the solution you'd like

Resource
|> Form.for_action(:create, api: App.Api)
|> Form.validate(%{name: "name"})
|> Form.submit()

Could be simplified to:

Resource
|> Form.for_action(:create)
|> Form.validate(%{name: "name"})
|> Form.submit()

A result of `AshPhoenix.Form.for_update/3` with resource that has has_one relationship is not valid

Describe the bug

A result of AshPhoenix.Form.for_update/3 with resource that has has_one relationship is not valid.
This might be not a bug, but I couldn't find the cause in the document.

post = %Post{
  id: Ash.UUID.generate(),
  title: "title",
  author: %Author{id: Ash.UUID.generate(), name: "name"}
}

form =
  post
  |> AshPhoenix.Form.for_update(:edit, forms: [auto?: true])
  |> to_form()

form.source.valid? # <== false

To Reproduce

https://gist.github.com/nallwhy/9460c228d77bd469b13d7740614a2882

Expected behavior

form.source.valid? is true.

Runtime

  • Elixir version: 1.16.0
  • Erlang version: 26.0
  • OS: Mac
  • Ash version: 2.19.10
  • any related extension versions: 1.3.1

inconsistent nested form in Phoenix.Html.Form

Describe the bug

when inspecting the Phoenix.Html.Form

{inspect form.source.forms[:nested] }

if I haven't added any forms with add_form, it gives []
but, if i add_form, then remove form, it is nil

Expected behavior
A clear and concise description of what you expected to happen.

** Runtime

  • Elixir version
  • Erlang version
  • OS
  • Ash version: main
  • any related extension versions

Additional context
Add any other context about the problem here.

Ash version to specific on ash_postgres v1.3.2

Description

ash_postgres v1.3.2 require as dependency {:ash, "~> v2.6.1"}, which is too specific.

To Reproduce

Try to require ash_postgres v1.3.2 with ash v2.19.14 it will fail to satisfied dependencies.

Expected behavior

ash_postgres v1.3.2 should be compatible with ash v2.19.14.

Runtime

  • Ash version: v2.19.14

AshPhoenix.Form doesn't mesh well with default resource values

When submitting a form outside of LiveView Phoenix will populate unfilled form values with "". Even if the given resource has default values after calling AshPhoenix.Form.submit what ends up being created is resource with attributes with the value of "". Furthermore if allow_nil? false is set then the validation will also fail. This leads to a situation where default values need to be manually set before validating and submitting the form even though the attribute has a default value set.

Example Controller

def new(conn, %{"new_list" => params}) do
    Todoish.Entries.List
    |> AshPhoenix.Form.for_create(:create,
      api: Todoish.Entries,
      transform_params: fn params, _ ->
        # Manually set default
        params =
          if params["title"] in ["", nil] do
            Map.put(params, "title", "A Todoish List")
          else
            params
          end

        params =
          # Manually set default
          if params["description"] in ["", nil] do
            Map.put(params, "description", "Add items to get started!")
          else
            params
          end

        Map.put(params, "url_id", Nanoid.generate())
      end
    )
    |> AshPhoenix.Form.validate(params)
    |> AshPhoenix.Form.submit()
    |> case do
      {:ok, result} ->
        IO.inspect(result)
        redirect(conn, to: "/#{result.url_id}")

      {:error, form} ->
        IO.inspect(form)

        conn
        |> put_flash(:error, "Uh-oh! Something went wrong. Please try again!")
        |> render("index.html", form: form)
    end
  end

Expected behavior
It would be ideal if there was a way to submit a form such that the default values of the attribute itself can be used instead of needing to manually check and fill them prior to submitting/validating the form.

Error when submitting nested form

Describe the bug
My app is crashing when I am trying to submit a nested form.

To Reproduce
Create a nested form. In my case, I have a cart with cart items where I would want to change the quantity of the cart items:

form =
  cart
  |> Form.for_update(:update,
    api: Api,
    forms: [
      items: [
        type: :list,
        data: cart.items,
        resource: CartItem,
        create_action: :create,
        update_action: :update
      ]
    ]
  )
  |> to_form()

Now submitting the data from this form is where it crashes. The params look like like this when the form changes:

Form.submit(form, params: %{
  "items" => %{
    "0" => %{
      "_form_type" => "update",
      "_persistent_id" => "0",
      "id" => "018d045e-8413-7dfe-b619-b468328242b3",
      "quantity" => "10"
    },
    "1" => %{
      "_form_type" => "update",
      "_persistent_id" => "1",
      "id" => "018d0e93-3019-75da-948b-8c9ebe60bfdf",
      "quantity" => "20"
    }
  }
})

The result is the app crashing with this error:

[error] GenServer #PID<0.1482.0> terminating
** (FunctionClauseError) no function clause matching in Puppy.Orders.CartItem.cast_input/2
    (puppy 0.1.0) lib/puppy/orders/resources/cart_item.ex:2: Puppy.Orders.CartItem.cast_input(%{"_form_type" => "update", "_persistent_id" => "1", "id" => "018d0e93-3019-75da-948b-8c9ebe60bfd
f", "quantity" => "20"}, [])
    (ash 2.18.1) lib/ash/type/type.ex:576: Ash.Type.cast_input/3
    (puppy 0.1.0) deps/ash/lib/ash/type/type.ex:1115: anonymous fn/3 in Puppy.Orders.CartItem.cast_input_array/2
    (elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
    (elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
    (ash 2.18.1) lib/ash/type/helpers.ex:10: Ash.Type.Helpers.cast_input/5
    (ash 2.18.1) lib/ash/changeset/changeset.ex:3784: Ash.Changeset.do_set_argument/3
    (stdlib 5.2) maps.erl:416: :maps.fold_1/4
    (ash 2.18.1) lib/ash/changeset/changeset.ex:1262: Ash.Changeset.handle_params/4
    (ash 2.18.1) lib/ash/changeset/changeset.ex:1196: Ash.Changeset.do_for_action/4
    (ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1096: AshPhoenix.Form.validate/3
    (ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1644: AshPhoenix.Form.submit/2
    (ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1629: AshPhoenix.Form.submit/2
    (puppy 0.1.0) lib/puppy_web/pages/cart_live.ex:63: PuppyWeb.CartLive.handle_event/3
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:497: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
    (telemetry 1.2.1) /home/tore/workspace/puppy/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:250: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
    (stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
    (stdlib 5.2) proc_lib.erl:251: :proc_lib.wake_up/3

Expected behavior
Either that the form will be submitted or an error that is pointing out what I am doing wrong.

Similar error also happening when adding a form
Form.add_form(form, [:items]) crashes with a similar error:

[error] #PID<0.567.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.566.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /cart
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Puppy.Orders.CartItem.cast_input/2
        (puppy 0.1.0) lib/puppy/orders/resources/cart_item.ex:2: Puppy.Orders.CartItem.cast_input(%{"_form_type" => "create"}, [])
        (ash 2.18.1) lib/ash/type/type.ex:576: Ash.Type.cast_input/3
        (puppy 0.1.0) deps/ash/lib/ash/type/type.ex:1115: anonymous fn/3 in Puppy.Orders.CartItem.cast_input_array/2
        (elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
        (elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
        (ash 2.18.1) lib/ash/type/helpers.ex:10: Ash.Type.Helpers.cast_input/5
        (ash 2.18.1) lib/ash/changeset/changeset.ex:3784: Ash.Changeset.do_set_argument/3
        (stdlib 5.2) maps.erl:416: :maps.fold_1/4
        (ash 2.18.1) lib/ash/changeset/changeset.ex:1262: Ash.Changeset.handle_params/4
        (ash 2.18.1) lib/ash/changeset/changeset.ex:1196: Ash.Changeset.do_for_action/4
        (ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1096: AshPhoenix.Form.validate/3
        (puppy 0.1.0) lib/puppy_web/pages/cart_live.ex:29: PuppyWeb.CartLive.mount/3
        (phoenix_live_view 0.20.3) lib/phoenix_live_view/utils.ex:354: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
        (telemetry 1.2.1) /home/tore/workspace/puppy/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.20.3) lib/phoenix_live_view/static.ex:281: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
        (phoenix_live_view 0.20.3) lib/phoenix_live_view/static.ex:116: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.20.3) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.7.10) lib/phoenix/router.ex:432: Phoenix.Router.__call__/5
        (puppy 0.1.0) lib/puppy_web/endpoint.ex:1: PuppyWeb.Endpoint.plug_builder_call/2
        (puppy 0.1.0) deps/plug/lib/plug/debugger.ex:136: PuppyWeb.Endpoint."call (overridable 3)"/2

Runtime

  • Elixir version: 1.16.0-otp-26
  • Erlang version: 26.2.1
  • OS: Endeavor OS 2023.08.05
  • Ash version: 2.18.1
  • Ash Phoenix: 1.2.26

Additional context
Add any other context about the problem here.

The cart has a relationship to cart items:

relationships do
  has_many :items, CartItem
end

Update action should also update the cart item:

update :update do
  primary? true

  argument :items, {:array, CartItem}

  change manage_relationship(:items, type: :append_and_remove)
end

Protocol Phoenix.HTML.FormData not implemented for #Ash.Changeset

Describe the bug
I'm creating login where I try to mix phx.gen.auth and example_with_auth. I hit this bug which prevents me from rendering forms.

To Reproduce
Create
This throws error above:
<.form let={f} for={@changeset} action={Routes.accounts_user_registration_path(@conn, :create)}>
eex equivalent: <%= form_for @changeset, Routes.accounts_user_registration_path(@conn, :create), fn f -> %>

When changing changeset for :user form works but is pretty much useless.
<.form let={f} for={:user} action={Routes.accounts_user_registration_path(@conn, :create)}>
eex equivalent: <%= form_for :user, Routes.accounts_user_registration_path(@conn, :create), fn f -> %>
Both heex and eex cause this bug.

Expected behavior
Form to be rendered into HTML.

** Runtime
I tried also latest master of ash and ash_phoenix with the same result.

  • Elixir version: 1.13.0
  • Erlang version: Erlang/OTP 25
  • OS: Ubuntu LTS 20.04
  • Ash version: 1.51.2
  • Ash_Phoenix: 0.7.1
  • Phoenix; 1.6.10
  • Phoenix_HTML: 3.2.0

Additional context
Maybe I'm using too complex forms here but I doubt it. My code is basically the same as user_registration_controller.ex and new.html.eex.

If you need access to code you can look on my GitLab. Prepare for mid upgrade mess on a small toy project with no tests. Just try to register. Oh and use env variables from .envrc.tmpl to get it up and running.
Url to trigger bug: http://localhost:4000/accounts/users/register

As you can tell I wasn't expecting someone else to use that code for anything. But I'm happy to test any changes that might help solve the issue.

Validation issue with v0.5.19-rc.1

Describe the bug
Validations are a bit broken - validation occurs, but errors are not appearing on the fields

To Reproduce
Create a resource with:

    attribute :note, :string do
      allow_nil?(false)
      default("HELLO")
    end

and a form

AshPhoenix.Form.for_create(Activity, :create,
        as: "activity",
        api: Projects
      )

Open the form, clear the "HELLO" value from the note, validate, validation occurs (error on the form), but no error message on the field. type something in the box, form error goes away, clear it again and error message does appear on the field this time...

Expected behavior
Error appears on the field

** Runtime

  • Elixir version
  • Erlang version
  • OS
  • Ash version
  • any related extension versions

Additional context
Add any other context about the problem here.

Rendering a form for a many-many relationship in an action that accepts an array of IDs crashes

Describe the bug

The form for a resource action that accepts an array of UUIDs for a many-many relationship, crashes before even rendering the form! :(

To Reproduce

Main resource (Element):

  relationships do 
    many_to_many :competencies, MyApp.Competency,
      source_attribute_on_join_resource: :element_id,
      destination_attribute_on_join_resource: :competency_id,
      through: MyApp.ElementCompetency
  end

  actions do
    create :create do
      primary? true
      argument :competency_ids, {:array, :uuid}
      change manage_relationship(:competency_ids, :competencies, type: :append_and_remove)
    end

Join table (ElementCompetency):

  relationships do
    belongs_to :element, MyApp.Element
    belongs_to :competency, MyApp.Competency
  end

  actions do
    defaults [:create, :read, :update, :destroy]
  end

Many-many relation (Competency):

  relationships do
    many_to_many :elements, MyApp.Element,
      source_attribute_on_join_resource: :competency_id,
      destination_attribute_on_join_resource: :element_id,
      through: MyApp.ElementCompetency
  end

  actions do
    defaults [:create, :read, :update, :destroy]
  end

Rendering the form for the Element create action raises the following error -

** (exit) an exception was raised:
    ** (CaseClauseError) no case clause matching: [{:join, :destroy, []}]
        (ash_phoenix 1.3.4) lib/ash_phoenix/form/auto.ex:613: AshPhoenix.Form.Auto.add_destroy_action/4
        (ash_phoenix 1.3.4) lib/ash_phoenix/form/auto.ex:382: anonymous fn/4 in AshPhoenix.Form.Auto.related/3
        (ash_phoenix 1.3.4) lib/ash_phoenix/form/form.ex:3224: anonymous fn/1 in AshPhoenix.Form.update_opts/3
        (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
        (elixir 1.15.4) lib/keyword.ex:1107: Keyword.update!/4
        (elixir 1.15.4) lib/keyword.ex:1111: Keyword.update!/4
        (ash_phoenix 1.3.4) lib/ash_phoenix/form/form.ex:441: AshPhoenix.Form.for_create/3
        (ash_admin 0.10.7) lib/ash_admin/components/resource/form.ex:1620: AshAdmin.Components.Resource.Form.assign_form/1
        (ash_admin 0.10.7) lib/ash_admin/components/resource/form.ex:37: AshAdmin.Components.Resource.Form.update/2
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/utils.ex:498: Phoenix.LiveView.Utils.maybe_call_update!/3
        (elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:685: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
        (telemetry 1.2.1) /Users/rebecca/Projects/work/my_client/my_app/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:680: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
        (stdlib 5.0.2) maps.erl:416: :maps.fold_1/4
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:639: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/static.ex:249: Phoenix.LiveView.Static.to_rendered_content_tag/4
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/static.ex:132: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.20.14) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3

Runtime

  • Elixir version 1.15.4-otp-26
  • Erlang version 26.0.2
  • OS macOS Sonoma
  • Ash version 2.20.1
  • AshAdmin 0.10.7

Missing logo file causes `mix doc` to fail.

Describe the bug
Building the documentation by running mix doc fails because in mix.exs file a non-existing "logos/small-logo.png"

To Reproduce
Just checkout the repo and build docs.

Expected behavior

The doc creation should produce the result in the docs folder.

** Runtime

  • Elixir version: 1.14.2
  • Erlang version: 25
  • OS: Win 11 (WSL2)
  • Ash version: 2.4.28

I can PR a fix which deletes the logo or can fix it if I get the logo which is intended to be used.

A helper function to aid in re-ordering records in nested forms

Is your feature request related to a problem? Please describe.

When integrating a library like SortableJS for drag and drop reordering of records in nested forms, it's currently super-cumbersome to properly reorder them within an AshPhoenix form so they don't re-render in their old order.

Describe the solution you'd like

Our current setup has SortableJS set up to send an LV event with the item paths in the desired order, eg. def handle_event("reposition", %{"items" => ["form[steps][1]", "form[steps][0]"]}, socket). Each resource (in this case Step) has a position field to determine the order it should appear in.

We can get the new position value for each record with Enum.with_index, so it would be awesome to be able to pass that in to a new helper function within AshPhoenix to do the setting of the values and re-ordering in one fell swoop.

It could be something like

def handle_event("reposition", %{"items" => paths}, socket) do 
  order_field = :position
  new_positions = Enum.with_index(paths)

  form = 
    socket.assigns.form
    |> AshPhoenix.reorder_nested(new_positions, order_field) # new helper?
  
  # ... rest

Describe alternatives you've considered

Currently we've (well, with your help) got it done inline manually, with some gnarly code that looks like the following:

    updates =
      paths
      |> Enum.with_index()
      |> Enum.map(fn {path, index} -> %{form_path: path, position: index} end)

    socket =
      Enum.reduce(updates, socket, fn %{form_path: path, position: position}, socket ->
        update_nested_form_value(socket, path, "position", position)
      end)
      |> update(:form, fn form ->
        Map.update!(form, :forms, fn forms ->
          Map.update!(forms, :steps, fn nested ->
            Enum.sort_by(nested, fn nested_form ->
              Ash.Changeset.get_attribute(nested_form.source, :position)
            end)
            |> Enum.map(fn nested_form ->
              position = AshPhoenix.Form.value(nested_form, :position)

              %{
                nested_form
                | name: form.name <> "[#{key}][#{position}]",
                  id: form.id <> "_#{key}_#{position}"
              }
            end)
          end)
        end)
      end)

where update_nested_form_value is defined as:

  defp update_nested_form_value(socket, form_path, field_name, value) do
    updated_form =
      AshPhoenix.Form.update_form(socket.assigns.form, form_path, fn form ->
        updated_params = Map.put(AshPhoenix.Form.params(form), field_name, value)
        AshPhoenix.Form.validate(form, updated_params)
      end)

    assign(socket, form: updated_form)
  end

Which works but it's not pretty by any means. Plus it would need to be copied and pasted and rewritten if we had a more deeply nested form.

It seems like a reasonable candidate for having a helper for it in AshPhoenix IMO!

Write a Phoenix Contexts -> Ash Resources guide

This guide can really help contextualize how to use Ash, and would be a great reference. We could start with running some phoenix generators, and then show how the same thing would be accomplished with Ash.

Form Validation crashes on invalid values in decimal-fields

Describe the bug

Form validation on an "for_update" form crashes with

** (Decimal.Error) : number parsing syntax: "x"

when passing a value for a decimal field which cannot be parsed to decimal (e.g. "x")

To Reproduce

error is triggered in call to Comp.not_equal? in AshPhoenix.Form.attributes_changed?/1
you can trigger the error standalone like this:

Comp.not_equal?(Elixir.Decimal.new("1.5"),"x")

Expected behavior

technically it is correct for Comp to fail when comparing apples and oranges.

But in this case i'd like attributes_changed?/1 to return true, because a change of attribute is present, although an invalid one.

the invalid format can then be catched later on and presents an "invalid value" message in the UI.

i solved it in my project by redefining Comparable.Comparable.Type.BitString.To.Decimal so that
comparing a decimal with a non-decimal always returns :gt

lib/my_app/my_decimal_comp.ex:

import Ash.Type.Comparable

defcomparable left :: Decimal, right :: BitString do
  case Decimal.cast(right) do
    :error -> :gt
    _ -> Decimal.compare(left, Ash.Type.Decimal.new(right))
  end
end


** Runtime **

  • Ash version
    2.4.20

`AshPhoenix.Form.value` returns the `string` value of the field instead of the `boolean` type.

Describe the bug
AshPhoenix.Form.value returns the string value of the field instead of the boolean type.
When the form is changed and the value of the field is the same as the initial.

To Reproduce

  1. The update Ash form is created from the resource with the field :enabled set to false function
    AshPhoenix.Form.value return correctly returns false.
  2. User changes the value to true AshPhoenix.Form.value return correctly returns true.
  3. User again changes the value to false AshPhoenix.Form.value return returns "false".
Screen.Recording.2024-03-07.at.12.59.49.mov
defmodule HelloWeb.Post do
  use Ash.Resource

  actions do
    defaults([:create, :read, :update, :destroy])
  end

  attributes do
    uuid_primary_key(:id)

    attribute(:enabled, :boolean)
  end
end

defmodule HelloWeb.PostsLive do
  use HelloWeb, :live_view
  alias HelloWeb.Post

  def render(assigns) do
    ~H"""
    <.form :let={f} for={@form} phx-change="change">
      <.input type="checkbox" field={f[:enabled]}  />
      <%= inspect(AshPhoenix.Form.value(@form, :enabled)) %>
    </.form>
    """
  end

  def mount(_params, _session, socket) do
    socket =
      assign(socket,
        form:
          AshPhoenix.Form.for_update(%Post{enabled: false}, :update)
          |> to_form()
      )

    {:ok, socket}
  end

  def handle_event("change", %{"form" => params}, socket) do
    form =
      AshPhoenix.Form.validate(
        socket.assigns.form,
        Map.merge(socket.assigns.form.params, params)
      )

    IO.inspect(AshPhoenix.Form.value(form, :enabled), label: "enabled")

    {:noreply, assign(socket, :form, form)}
  end
end

Expected behavior
Return the same value for the field not depending on whether the form was edited or is untouched.

Runtime

  • Elixir 1.14.3-otp-25
  • Erlang 26.1.2
  • Mac Os 14.2.1
  • Ash 2.19.14
  • Ash phoenix 1.3

Setting actor in nested forms

Describe the bug
A clear and concise description of what the bug is. If you are not sure if the bug is related to ash or an extension, log it with ash and we will move it.

This may be a user error rather than a bug, but I'm porting an issue here per Zach's guidance.

The user info set into the process with Ash.set_actor is only accessible from the main form's (create/update) actions, but not from its sub-forms'. In the sub-forms' action, actor returns nil.

To Reproduce
A minimal set of resource definitions and calls that can reproduce the bug.

Run Ash.set_actor(current_user) in a liveview's mount function and check the value of actor in the create action of a sub-form to observe its value being nil.

Expected behavior
A clear and concise description of what you expected to happen.

actor should return the user info set by Ash.set_actor across different nested forms.

** Runtime

  • Elixir version 1.14.1
  • Erlang version Erlang/OTP 25
  • OS Windows 11
  • Ash version 2.13.3
  • any related extension versions

Additional context
Add any other context about the problem here.

Setting actor as follows, without using Ash.set_actor in the mount function, causes a nil actor value in every form's action including the main form's. Again, this may be a user error, but mentioning it here in case this problem is somehow related to the main issue reported above.


AshPhoenix.Form.for_update(post, :create,
    api: MyAPI,
    actor: current_user,
    forms: [
         comments: [
              resources: Comment,
               ...

`keep_live` access callback result in `subscribe`

Describe the bug

When setting up a keep_live it's currently impossible to access the callback result for setting up any subscriptions.

To Reproduce
A minimal set of resource definitions and calls that can reproduce the bug.

socket
|> keep_live(
  :merge_request,
  fn socket ->
    Nxy.Workspace.MergeRequest.get_by_id!(params["id"])
  end,
  subscribe: [
    "merge_request:updated:#{socket.assigns.merge_request.project_id}" # <-- unable to access the project_id here
  ]
)

Expected behavior

subscription should could be a callback that get's the result of the previous callback.

`Form.for_create` and `Form.for_update` don't seem to validate that the action exists with that method

Describe the bug

I noticed this when I was badly copying-pasting actions in a resource - I had defined

# should be update :update, oops
create :update do 
  accept list_of_fields
end

My form instantiation code looked like:

Form.for_update(album, :update, api: MyApp.Music)

Which worked when rendering the form, but then on change gave a validation error on any fields that were allow_nil? false.

I tested inverting it as well - having an update :create and calling for_create, and it seemed to do a very similar thing.

Expected behavior

I would expect the for_update(record, :update, api: whatever) call to raise an error if there is no update action named :update.

It could be very clever and say something like "No update action found named :update, but did find a create action with the same name. Did you mean for_create?".

Runtime

  • Elixir version 1.15.7-otp-26
  • Erlang version 26.1.2
  • Ash version 2.17.20
  • AshPostgres version 1.2.25

Duplicate error messages on embedded form

Describe the bug
I have an action that given invalid input will generate 2 errors. But when given the same input in an AshPhoenix.Form, the same invalid input will generate each error twice.

I wrote the following test to duplicate the issue in my app.

    test "should not duplicate error messages", %{
      environment: environment,
      feature: feature,
      owner: owner
    } do
      # This assertation passes. There are 2 errors: key is required, value is required
      assert {:error,
              %Ash.Error.Invalid{
                errors: [
                  %Ash.Error.Changes.Required{
                    field: :key,
                    type: :attribute,
                    resource: MyApp.Environments.FeatureVariant.String,
                    changeset: nil,
                    query: nil,
                    error_context: [],
                    vars: [],
                    path: [:variant],
                    class: :invalid
                  },
                  %Ash.Error.Changes.Required{
                    field: :value,
                    type: :attribute,
                    resource: MyApp.Environments.FeatureVariant.String,
                    changeset: nil,
                    query: nil,
                    error_context: [],
                    vars: [],
                    path: [:variant],
                    class: :invalid
                  }
                ]
              }} =
               MyApp.Environments.Feature.add_variant(
                 feature,
                 %{
                   variant: %{
                     description: "",
                     enabled: "true",
                     key: "",
                     metadata: "{}",
                     type: "string",
                     value: ""
                   }
                 },
                 actor: owner,
                 tenant: environment.organization_id
               )

      # This should be the same as above, except going AshPhoenix.Form, but there are now 4 errors.
      assert {:error,
              %{
                source: %{
                  forms: %{
                    variant: %{
                      # submit_errors does not match.
                      # I expect 2 errors, but there are 4. Each of the 2 errors is duplicated.
                      submit_errors: [value: {"is required", []}, key: {"is required", []}]
                    }
                  }
                }
              }} =
               AshPhoenix.Form.for_update(feature, :add_variant,
                 api: MyApp.Environments,
                 as: "feature",
                 forms: [
                   auto?: true
                 ],
                 actor: owner,
                 tenant: environment.organization_id
               )
               |> Phoenix.Component.to_form()
               |> AshPhoenix.Form.add_form(:variant, params: %{"type" => "string"})
               |> AshPhoenix.Form.submit(
                 params: %{
                   "variant" => %{
                     "_form_type" => "create",
                     "_persistent_id" => "0",
                     "_touched" => "_form_type,_touched,type",
                     "description" => "",
                     "enabled" => "true",
                     "key" => "",
                     "metadata" => "{}",
                     "type" => "string",
                     "value" => ""
                   }
                 }
               )
    end

To Reproduce
I can reliably reproduce this in my app. I don't have an easy way to replicate it outside of my app though.

Expected behavior
The errors should not duplicate

** Runtime
I'm on the latest Ash Versions.

Additional context
This form has an embedded form for an argument that is an embedded resource (but not a relationship). That resource is also a union of models.

mix ash_phoenix.gen.live Error

Describe the bug
I ran mix ash_phoenix.gen.live and got a syntax error on the generated code.

mix ash_phoenix.gen.live Red.Api Red.Api.Attempt

Compiling 1 file (.ex)
warning: the Inspect protocol has already been consolidated, an implementation for Red.Api.User has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation
  lib/red/api/resources/user.ex:1

warning: the Inspect protocol has already been consolidated, an implementation for Red.Api.User has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation
  lib/red/api/resources/user.ex:1

Would you like to name your actor? For example: `current_user`. If you choose no, we will not add any actor logic. [Yn] y
What would you like to name it? For example: `current_user` current_user
Please provide a plural_name. For example the plural of tweet is tweets.
You can press enter to abort, and then configure one on the resource, for example:

    resource do
      plural_name "tweets"
    end
> attempts
Primary destroy action not found, and a destroy action not supplied. Would you like to create one? [Yn] n
** (SyntaxError) lib/red_web/live/attempt_live/index.ex:80:96: unexpected token: }

    HINT: the "(" on line 80 is missing terminator ")"

    |
 80 |       |> assign(:attempt, Red.Api.get!(Red.Api.Attempt, id, actor: socket.assigns.current_user)})
    |                                                                                                ^
    (elixir 1.15.4) lib/code.ex:945: Code.format_string!/2
    (mix 1.15.4) lib/mix/tasks/format.ex:597: Mix.Tasks.Format.elixir_format/2
    (ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:162: AshPhoenix.Gen.Live.write_formatted_template/5
    (ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:97: AshPhoenix.Gen.Live.generate/3
    (mix 1.15.4) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.15.4) lib/mix/cli.ex:92: Mix.CLI.run_task/2
    /Users/dewet/.asdf/installs/elixir/1.15.4-otp-26/bin/mix:2: (file)

To Reproduce
I have a very minimal starter app here https://github.com/dewetblomerus/red

Expected behavior
It should generate code

Runtime

  • Elixir version 1.15.4
  • Erlang version 26
  • OS MacOs
  • Ash version 2.14.20
  • AshPhoenix version 1.2.17
  • any related extension versions

Additional context
Even if I did something wrong, there should never be a syntax error in generated code.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.