Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add array enum support #107

Merged
merged 1 commit into from
Oct 17, 2024

Conversation

davorbadrov
Copy link
Contributor

Hi, and thanks for the lib. I've been using it in a few projects and it's quite handy.

I've noticed that it doesn't support {:array, :enum} definitions, like the following:

  defparams :arary_list_example do
    required :array_list, {:array, :enum}, rules: [values: [:active, :inactive]]
    required :array_list_2, {:array, :enum}, rules: [values: [:active, :inactive], min: 1]
  end

This PR adds the support and a few tests showing how they're used. Let me know if this is something you'd want merged and if any changes are required. I'm willing to add examples to the docs as well.

@martinthenth
Copy link
Owner

Hi @davorbadrov! Thanks for your PR. I am interested in knowing more about the use-cases and whether they fit in the existing features (like an array of strings)

@davorbadrov
Copy link
Contributor Author

Hi Martin,

my usage of the lib is for form validations mainly, but I'm also experimenting for using it for scenarios where I'm passing maps into internal business logic, to ensure that they're adhering to a strict contract.

For form validations, when working with multiselects or something similar, this can both validate and parse the incoming values from HTML forms into atoms.

# Here's how I use it currently
defmodule App.UserRegistrationLive do
  # ...
  use Goal
  
  defparams :registration_form do
    required(:email, :string)
    required(:password, :string)
    required(:subscriptions, {:array, :string}, min: 1, rules: [included: ["marketing", "critical", "some_other_value"]])
  end

  def handle_event("register", params, socket) do
    with {:ok, attrs} <- validate(:registration_form, params),
        {:ok, attrs} <- convert_subscriptions_to_atoms(attrs),
        {:ok, user} <- register_user(attrs) do
      {:noreply, assign(socket, user_registered: true)}
    else
      error ->
        handle_error_response(socket, params, error, &assign_unsubscribe_sms_form/3)
    end
  end
  
  def convert_subscriptions_to_atoms(attrs) do
    # Here I need to manually convert the subscriptions to atoms,
    # make sure to use the String.to_existing_atom/1
    # ensure there's at least a single item in there,
    # and that the field consists of a valid set of items,
    # if something's amiss, then I need to create a changeset with an error here.
    #
    # Everything that Goal usually does, expect it doesn't do it with an array of enums.
  end
  
  @spec register_user(%{
    email: String.t(),
    password: String.t(),
    subscriptions: [UserSubscription.t()]
  }) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  def register_user(user_attrs) do
    # ...
  end

  # ...
end

# This would be the preferred approach
defmodule App.UserRegistrationLive do
  # ...
  use Goal
  
  defparams :registration_form do
    required(:email, :string)
    required(:password, :string)
    required(:subscriptions, {:array, :enum}, min: 1, rules: [values: [:marketing, :critical, :some_other_value]])
  end

  def handle_event("register", params, socket) do
    with {:ok, attrs} <- validate(:registration_form, params),
         # no need for manual conversion here
         {:ok, user} <- register_user(attrs) do
      {:noreply, assign(socket, user_registered: true)}
    else
      error ->
        handle_error_response(socket, params, error, &assign_unsubscribe_sms_form/3)
    end
  end
  
  @spec register_user(%{
    email: String.t(),
    password: String.t(),
    subscriptions: [UserSubscription.t()]
  }) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
  def register_user(user_attrs) do
    # ...
  end

  # ...
end

@martinthenth
Copy link
Owner

Thanks for the example, it is an interesting use-case and worth implementing! I'll take a look at your code when I have some time

@martinthenth martinthenth merged commit 97133f6 into martinthenth:main Oct 17, 2024
1 check passed
@martinthenth
Copy link
Owner

Looks good to me! Thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants