defmodule Mv.Membership.CustomFieldValue do @moduledoc """ Ash resource representing a custom field value for a member. ## Overview CustomFieldValues implement the Entity-Attribute-Value (EAV) pattern, allowing dynamic custom fields to be attached to members. Each custom field value links a member to a custom field and stores the actual value. ## Value Storage Values are stored using Ash's union type with JSONB storage format: ```json { "type": "string", "value": "example" } ``` ## Supported Types - `:string` - Text data - `:integer` - Numeric data - `:boolean` - True/false flags - `:date` - Date values - `:email` - Validated email addresses (custom type) ## Relationships - `belongs_to :member` - The member this custom field value belongs to (CASCADE delete) - `belongs_to :custom_field` - The custom field definition (CASCADE delete) ## Constraints - Each member can have only one custom field value per custom field (unique composite index) - Custom field values are deleted when the associated member is deleted (CASCADE) - Custom field values are deleted when the associated custom field is deleted (CASCADE) - String values maximum length: 10,000 characters - Email values maximum length: 254 characters (RFC 5321) ## Future Features - Type-matching validation (value type must match custom field's value_type) - to be implemented """ use Ash.Resource, domain: Mv.Membership, data_layer: AshPostgres.DataLayer postgres do table "custom_field_values" repo Mv.Repo references do reference :member, on_delete: :delete reference :custom_field, on_delete: :delete end end actions do defaults [:create, :read, :update, :destroy] default_accept [:value, :member_id, :custom_field_id] read :by_custom_field_id do argument :custom_field_id, :uuid, allow_nil?: false filter expr(custom_field_id == ^arg(:custom_field_id)) end end attributes do uuid_primary_key :id attribute :value, :union, constraints: [ storage: :type_and_value, types: [ boolean: [ type: :boolean ], date: [ type: :date ], integer: [ type: :integer ], string: [ type: :string, constraints: [ max_length: 10_000, trim?: true ] ], email: [ type: Mv.Membership.Email ] ] ] end relationships do belongs_to :member, Mv.Membership.Member belongs_to :custom_field, Mv.Membership.CustomField end calculations do calculate :value_to_string, :string, expr(value[:value] <> "") end # Ensure a member can only have one custom field value per custom field # For example: A member can have only one "phone" custom field value, one "email" custom field value, etc. identities do identity :unique_custom_field_per_member, [:member_id, :custom_field_id] end end