How to nest a has_many within an AshAuthentication user registration

My User resource has_many :credits, Animina.Accounts.Credit. When a new user registers him/herself I want to add one credit with the amount of 100 points automatically. But I have no idea how to nest it in the registration form. How does that work?

The LiveView:

defp apply_action(socket, :register, _params) do
  socket
  |> assign(:form_id, "sign-up-form")
  |> assign(:cta, "Register new user")
  |> assign(:action, ~p"/auth/user/password/register")
  |> assign(
    :form,
    Form.for_create(BasicUser, :register_with_password, api: Accounts, as: "user")
  )
end

def render(assigns) do
  ~H"""
    <.form :let={f} for={@form} action={@action} method="POST" class="space-y-6">
          <%= text_input(f, :email, type: :email, required: true) %>
          <%= password_input(f, :password) %>
    <%= submit(@cta) %>
"""

User resource

defmodule Animina.Accounts.User do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication]

  attributes do
    uuid_primary_key :id
    attribute :email, :ci_string, allow_nil?: false
    attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
  end

  relationships do
    has_many :credits, Animina.Accounts.Credit
  end
  # ....

Credit resource

defmodule Animina.Accounts.Credit do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer

  attributes do
    uuid_primary_key :id

    attribute :points, :integer do
      constraints min: 1,
                  max: 10_000
    end

    attribute :subject, :string do
      constraints max_length: 50,
                  min_length: 1,
                  trim?: true,
                  allow_empty?: false
    end
  end

  relationships do
    belongs_to :user, Animina.Accounts.User do
      allow_nil? false
      attribute_writable? true
    end
  end

  postgres do
    table "credits"
    repo Animina.Repo

    references do
      reference :user, on_delete: :delete
    end
  end

  actions do
    defaults [:create, :read]
  end

  code_interface do
    define_for Animina.Accounts
    define :read
    define :create
    define :by_id, get_by: [:id], action: :read
  end
end

See full code at GitHub - animina-dating/animina: Dating Platform

If its a static “add 100 credits”, I would likely hook into registration with a global change.

changes do
  change Add100Credits, where: [action_is(:register_with_password)]
end

Then you can use something like Ash.Changeset.manage_relationship, or Ash.Changeset.after_action to do your work.

1 Like

would really like a full blown example of this. i just don’t get how the pieces fit together. been trying to use after_action for a while and nothing makes sense. my usecase is i’m trying to create a default team for a user that just registered. seems like an impossible mission. found so many wrong ways of doing it.

edit:

solved my case with the help of discord.

so just chucked this into the USer resource

  changes do
    change CreateDefaultTeam, on: [:create]
  end

and then created that module somewhere

defmodule CreateDefaultTeam do
  use Ash.Resource.Change

  def change(changeset, _opts, _context) do
    email = Ash.Changeset.get_attribute(changeset, :email)

    Ash.Changeset.manage_relationship(
      changeset,
      :teams,
      %{name: "#{email}'s Team"},
      type: :create
    )
  end
end

I just learnt that I can conditionally apply global changes! Awesome staff.