clifinger

clifinger

Hello,

I need Help!

I am totally new with Ash and I want to do a login action with a password verification,
Should I do an action or a function in my module ?

Showing Posts 19 to 10

zachdaniel

zachdaniel

Creator of Ash

It is on the short list to allow validations to be run against queries, which will make this experience much nicer. i.e validate match(:email, ~r//), message: "..." For now, however, preparations is the only way. Sorry about that!

clifinger

clifinger OP

I ended up by:

defmodule VsApp.Accounts.Constants do
  @moduledoc """
  Centralized constants for account-related validations and messages.
  """

  @validation_rules %{
    email: %{
      regex: ~r/^[\w.+-]+@\w+\.\w+$/,
      max_length: 160,
      messages: %{
        format: "Invalid email format",
        length: "Email should have max length of 160",
        presence: "Email must be present",
        unique: "Email should be unique"
      }
    },
    password: %{
      min_length: 6,
      messages: %{
        length: "Password should have min length of 6",
        presence: "Password must be present"
      }
    }
  }

  # Getters for validations
  def email_regex, do: get_in(@validation_rules, [:email, :regex])
  def email_max_length, do: get_in(@validation_rules, [:email, :max_length])
  def password_min_length, do: get_in(@validation_rules, [:password, :min_length])

  # Getters for messages
  def message(:email, key), do: get_in(@validation_rules, [:email, :messages, key])
  def message(:password, key), do: get_in(@validation_rules, [:password, :messages, key])

  # Validation helper
  def valid_email?(email) do
    email && is_binary(email) && Regex.match?(email_regex(), email)
  end
end

And:

defmodule VsApp.Accounts.Validations.ValidLoginPreparation do
  use Ash.Resource.Preparation
  require Logger
  require Ash.Query
  alias VsApp.Accounts.Constants

  @impl true
  def init(opts), do: {:ok, opts}

  @impl true
  def prepare(query, _opts, _context) do
    password = Ash.Query.get_argument(query, :password)
    email = Ash.Query.get_argument(query, :email)

    cond do
      is_nil(email) or not is_binary(email) ->
        Ash.Query.add_error(query, field: :email, message: Constants.message(:email, :presence))

      not Regex.match?(Constants.email_regex(), email) ->
        Ash.Query.add_error(query, field: :email, message: Constants.message(:email, :format))

      String.length(email) > Constants.email_max_length() ->
        Ash.Query.add_error(query, field: :email, message: Constants.message(:email, :length))

      is_nil(password) or not is_binary(password) ->
        Ash.Query.add_error(query,
          field: :password,
          message: Constants.message(:password, :presence)
        )

      String.length(password) < Constants.password_min_length() ->
        Ash.Query.add_error(query,
          field: :password,
          message: Constants.message(:password, :length)
        )

      true ->
        query
        |> Ash.Query.filter(email == ^email)
        |> Ash.Query.load([:hashed_password])
        |> Ash.Query.after_action(fn _, user ->
          case user do
            [user] ->
              if Argon2.verify_pass(password, user.hashed_password) do
                {:ok, [user]}
              else
                {:ok, []}
              end

            [] ->
              Argon2.no_user_verify()
              {:ok, []}

            _ ->
              {:ok, []}
          end
        end)
    end
  end
end
zachdaniel

zachdaniel

Creator of Ash

Anonymous functions are just one way to define changes/preparations. You can use modules that implement the appropriate behavior.

prepare PreparationModule
....
validate ValidationModule
...
change ChangeModule

In all of those cases you can also add opts to reuse the behavior but customize per use.

change {SomeChange, opt: :value}

You can’t use a change as a preparation or a validation etc. so within those modules if you have to share logic you do it with “regular functions”. I.e if you want a function that takes a string and tells you if it’s a valid email, you can put that in a module alongside your resource and call into it from the resource.

Each of the ways of using those types of modules I mentioned above is explained in its respective guide: Changes — ash v3.29.3

The book also goes into this and how to use/think about it in detail.

clifinger

clifinger OP

Ok I will buy it asap.

Can you help me please ? :

zachdaniel

zachdaniel

Creator of Ash
clifinger

clifinger OP

My frustration got away :

create :register do
      argument :password, :string, allow_nil?: false
      accept [:email]

      validate present(:email), message: "Email must be present"
      validate present(:password), message: "Password must be present"

      change fn changeset, _ ->
        changeset |> maybe_hash_password()
      end
    end
  end

  defp maybe_hash_password(changeset) do
    password = Ash.Changeset.get_argument(changeset, :password)

    if password && changeset.valid? do
      changeset
      |> Ash.Changeset.change_attribute(:hashed_password, Argon2.hash_pwd_salt(password))
      |> Ash.Changeset.clear_change(:password)
    else
      changeset
    end
  end

Both Login and register work :wink:

clifinger

clifinger OP

Where I can buy the book ?

Quick question, I tested with

And it work, but something bother me as I need to duplicate the global validations.
Do you have a hint to reuse some validations from resource in another module ?
Maybe it’s more relative to Elixir and my level but I will appreciate if you can help me on this one :wink:

zachdaniel

zachdaniel

Creator of Ash

Definitely. No denying it :slight_smile: We’ll get it sorted, but likely not in the next few days :sweat: The book has been released into beta, so that’s also a good resource to learn.

clifinger

clifinger OP

I will switch to it by tomorrow :slight_smile:

clifinger

clifinger OP

I’m fully aware of the work you’ve accomplished, and I believe I’ve gone through almost all your talks on YouTube. I’m going to give myself a little more time, about two more days, before making a final decision. I’m not saying this out of ego, but because I need to make a decisive choice for the company where I work as CTO, which leans towards a more full TypeScript/Node.js stack.

What reassures me is your availability and, of course, all the hard work you’ve put in so far. However, to be completely transparent and honest, I genuinely think the documentation has gaps. I won’t hesitate to contribute once I’m ready myself.

Thank you again for your response!

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews