defmodule Mv.Membership.Property do @moduledoc """ Ash resource representing a custom property value for a member. ## Overview Properties implement the Entity-Attribute-Value (EAV) pattern, allowing dynamic custom fields to be attached to members. Each property links a member to a property type 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 property belongs to (CASCADE delete) - `belongs_to :property_type` - The property type definition ## Constraints - Each member can have only one property per property type (unique composite index) - Properties are deleted when the associated member is deleted (CASCADE) """ use Ash.Resource, domain: Mv.Membership, data_layer: AshPostgres.DataLayer postgres do table "properties" repo Mv.Repo references do reference :member, on_delete: :delete end end actions do defaults [:create, :read, :update, :destroy] default_accept [:value, :member_id, :property_type_id] 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], email: [type: Mv.Membership.Email] ] ] end relationships do belongs_to :member, Mv.Membership.Member belongs_to :property_type, Mv.Membership.PropertyType end calculations do calculate :value_to_string, :string, expr(value[:value] <> "") end # Ensure a member can only have one property per property type # For example: A member can have only one "email" property, one "phone" property, etc. identities do identity :unique_property_per_member, [:member_id, :property_type_id] end end