README.md

# AshCanonicalIdentity

Generate identity, get_by/list_by actions and code_interface for unique keys.

## Documentation

- [DSL Reference](documentation/dsls/ash-canonical-identity.md)
- [API Documentation](https://hexdocs.pm/ash_canonical_identity)

## Installation

Add `ash_canonical_identity` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ash_canonical_identity, "~> 0.3.0"}
  ]
end
```

## Usage

```elixir
defmodule PostTag do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshCanonicalIdentity]

  attributes do
    uuid_primary_key :id
    attribute :tag, :string, allow_nil?: false, public?: true
  end

  actions do
    defaults [:read, :destroy, create: :*, update: :*]
  end

  relationships do
    belongs_to :post, Post, allow_nil?: false, public?: true
  end

  canonical_identities do
    identity [:post, :tag]
  end
end
```

The `identity [:post, :tag]` generates:

- `get_by_post_tag(post_id, tag)` - returns single record
- `list_by_post_tag(values)` - returns multiple records

### Options

```elixir
canonical_identities do
  identity [:post, :tag],
    name: :custom_name,           # default: :auto (generates :post_tag)
    get_action: :custom_get,      # default: :auto (generates :get_by_post_tag), false to disable
    list_action: :custom_list,    # default: :auto (generates :list_by_post_tag), false to disable
    where: expr(active == true),  # optional filter
    nils_distinct?: true          # default: true
end
```

### Examples

```elixir
# get_by - single record
PostTag.get_by_post_tag!(post_id, "elixir")

# list_by - multiple records (tuple order matches get_by args)
PostTag.list_by_post_tag!([
  {post1_id, "elixir"},
  {post2_id, "phoenix"}
])
```

### Generated Code Equivalent

The `identity [:post, :tag]` has the same effect as:

```elixir
identities do
  identity :post_tag, [:post_id, :tag]
end

actions do
  read :get_by_post_tag do
    get? true
    argument :post_id, :uuid, allow_nil?: true
    argument :tag, :string, allow_nil?: true
    filter expr(post_id == ^arg(:post_id) and tag == ^arg(:tag))
  end

  read :list_by_post_tag do
    argument :values, {:array, :term}, allow_nil?: false
    prepare {AshCanonicalIdentity.ListPreparation, attr_names: [:post_id, :tag], where: nil}
  end
end

code_interface do
  define :get_by_post_tag, args: [:post_id, :tag]
  define :list_by_post_tag, args: [:values]
end
```

## License

MIT