From 9af73818439787fa4cf1ef485e4c5d32645eaf81 Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 8 Jan 2026 11:22:44 +0100 Subject: [PATCH] refactor: extract helper modules to remove code duplication --- lib/mv/helpers/type_parsers.ex | 49 +++++++++++++++ .../membership/helpers/visibility_config.ex | 55 +++++++++++++++++ lib/mv_web/helpers/field_type_formatter.ex | 59 +++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 lib/mv/helpers/type_parsers.ex create mode 100644 lib/mv/membership/helpers/visibility_config.ex create mode 100644 lib/mv_web/helpers/field_type_formatter.ex diff --git a/lib/mv/helpers/type_parsers.ex b/lib/mv/helpers/type_parsers.ex new file mode 100644 index 0000000..6c07e6e --- /dev/null +++ b/lib/mv/helpers/type_parsers.ex @@ -0,0 +1,49 @@ +defmodule Mv.Helpers.TypeParsers do + @moduledoc """ + Helper functions for parsing various input types to common Elixir types. + + Provides safe parsing functions for common type conversions, especially useful + when dealing with form data or external APIs. + """ + + @doc """ + Parses various input types to boolean. + + Handles: booleans, strings ("true"/"false"), integers (1/0), and other values (defaults to false). + + ## Parameters + + - `value` - The value to parse (boolean, string, integer, or other) + + ## Returns + + A boolean value + + ## Examples + + iex> parse_boolean(true) + true + + iex> parse_boolean("true") + true + + iex> parse_boolean("false") + false + + iex> parse_boolean(1) + true + + iex> parse_boolean(0) + false + + iex> parse_boolean(nil) + false + """ + @spec parse_boolean(any()) :: boolean() + def parse_boolean(value) when is_boolean(value), do: value + def parse_boolean("true"), do: true + def parse_boolean("false"), do: false + def parse_boolean(1), do: true + def parse_boolean(0), do: false + def parse_boolean(_), do: false +end diff --git a/lib/mv/membership/helpers/visibility_config.ex b/lib/mv/membership/helpers/visibility_config.ex new file mode 100644 index 0000000..886d575 --- /dev/null +++ b/lib/mv/membership/helpers/visibility_config.ex @@ -0,0 +1,55 @@ +defmodule Mv.Membership.Helpers.VisibilityConfig do + @moduledoc """ + Helper functions for normalizing member field visibility configuration. + + Handles conversion between string keys (from JSONB) and atom keys (Elixir convention). + JSONB in PostgreSQL converts atom keys to string keys when storing. + This module provides functions to normalize these back to atoms for Elixir usage. + """ + + @doc """ + Normalizes visibility config map keys from strings to atoms. + + JSONB in PostgreSQL converts atom keys to string keys when storing. + This function converts them back to atoms for Elixir usage. + + ## Parameters + + - `config` - A map with either string or atom keys + + ## Returns + + A map with atom keys (where possible) + + ## Examples + + iex> normalize(%{"first_name" => true, "email" => false}) + %{first_name: true, email: false} + + iex> normalize(%{first_name: true, email: false}) + %{first_name: true, email: false} + + iex> normalize(%{"invalid_field" => true}) + %{} + """ + @spec normalize(map()) :: map() + def normalize(config) when is_map(config) do + Enum.reduce(config, %{}, fn + {key, value}, acc when is_atom(key) -> + Map.put(acc, key, value) + + {key, value}, acc when is_binary(key) -> + try do + atom_key = String.to_existing_atom(key) + Map.put(acc, atom_key, value) + rescue + ArgumentError -> acc + end + + _, acc -> + acc + end) + end + + def normalize(_), do: %{} +end diff --git a/lib/mv_web/helpers/field_type_formatter.ex b/lib/mv_web/helpers/field_type_formatter.ex new file mode 100644 index 0000000..6cc86e6 --- /dev/null +++ b/lib/mv_web/helpers/field_type_formatter.ex @@ -0,0 +1,59 @@ +defmodule MvWeb.Helpers.FieldTypeFormatter do + @moduledoc """ + Helper functions for formatting field types for display. + + Handles both Ash type modules (e.g., `Ash.Type.String`) and simple atoms (e.g., `:string`). + """ + + alias MvWeb.Translations.FieldTypes + + @doc """ + Formats an Ash type for display. + + Handles both Ash type modules (e.g., `Ash.Type.String`) and simple atoms (e.g., `:string`). + + ## Parameters + + - `type` - An atom or module representing the field type + + ## Returns + + A human-readable string representation of the type + + ## Examples + + iex> format(:string) + "String" + + iex> format(Ash.Type.String) + "String" + + iex> format(Ash.Type.Date) + "Date" + """ + @spec format(atom() | module()) :: String.t() + def format(type) when is_atom(type) do + type_string = to_string(type) + + if String.contains?(type_string, "Ash.Type.") do + type_string + |> String.split(".") + |> List.last() + |> String.downcase() + |> then(fn type_name -> + try do + type_atom = String.to_existing_atom(type_name) + FieldTypes.label(type_atom) + rescue + ArgumentError -> FieldTypes.label(:string) + end + end) + else + FieldTypes.label(type) + end + end + + def format(type) do + to_string(type) + end +end