# `JSONAPI.View`
[🔗](https://github.com/beam-community/jsonapi/blob/main/lib/jsonapi/view.ex#L2)

A View is simply a module that defines certain callbacks to configure proper
rendering of your JSONAPI documents.

    defmodule PostView do
      use JSONAPI.View

      def fields, do: [:id, :text, :body]
      def type, do: "post"
      def relationships do
        [author: UserView,
         comments: CommentView]
      end
    end

    defmodule UserView do
      use JSONAPI.View

      def fields, do: [:id, :username]
      def type, do: "user"
      def relationships, do: []
    end

    defmodule CommentView do
      use JSONAPI.View

      def fields, do: [:id, :text]
      def type, do: "comment"
      def relationships do
        [user: {UserView, :include}]
      end
    end

    defmodule DogView do
      use JSONAPI.View, namespace: "/pupperz-api"
    end

You can now call `UserView.show(user, conn, conn.params)` and it will render
a valid jsonapi doc.

## Fields

By default, the resulting JSON document consists of fields, defined in the `fields/0`
function. You can define custom fields or override current fields by defining a
2-arity function inside the view that takes `data` and `conn` as arguments and has
the same name as the field it will be producing. Refer to our `fullname/2` example below.

    defmodule UserView do
      use JSONAPI.View

      def fullname(data, conn), do: "fullname"

      def fields, do: [:id, :username, :fullname]
      def type, do: "user"
      def relationships, do: []
    end

Fields may be omitted manually using the `hidden/1` function.

    defmodule UserView do
      use JSONAPI.View

      def fields, do: [:id, :username, :email]

      def type, do: "user"

      def hidden(_data) do
        [:email] # will be removed from the response
      end
    end

In order to use [sparse fieldsets](https://jsonapi.org/format/#fetching-sparse-fieldsets)
you must include the `JSONAPI.QueryParser` plug.

If you want to fetch fields from the given data *dynamically*, you can use the
`c:get_field/3` callback.

    defmodule UserView do
      use JSONAPI.View

      def fields, do: [:id, :username, :email]

      def type, do: "user"

      def get_field(field, data, _conn) do
        Map.fetch!(data, field)
      end
    end

## Relationships

Currently the relationships callback expects that a map is returned
configuring the information you will need. If you have the following Ecto
Model setup

    defmodule User do
      schema "users" do
        field :username
        has_many :posts
        has_one :image
      end
    end

and the includes setup from above. If your Post has loaded the author and the
query asks for it then it will be loaded.

So for example:
`GET /posts?include=post.author` if the author record is loaded on the Post, and you are using
the `JSONAPI.QueryParser` it will be included in the `includes` section of the JSONAPI document.

If you always want to include a relationship. First make sure its always preloaded
and then use the `[user: {UserView, :include}]` syntax in your `includes` function. This tells
the serializer to *always* include if its loaded.

## Polymorphic Resources

Polymorphic resources allow you to serialize different types of data with the same view module.
This is useful when you have a collection of resources that share some common attributes but
have different types, fields, or relationships based on the specific data being serialized.

To enable polymorphic resources, set `polymorphic_resource?: true` when using the JSONAPI.View:

    defmodule MediaView do
      use JSONAPI.View, polymorphic_resource?: true

      def polymorphic_type(%{type: "image"}), do: "image"
      def polymorphic_type(%{type: "video"}), do: "video"
      def polymorphic_type(%{type: "audio"}), do: "audio"

      def polymorphic_fields(%{type: "image"}), do: [:id, :url, :width, :height, :alt_text]
      def polymorphic_fields(%{type: "video"}), do: [:id, :url, :duration, :thumbnail]
      def polymorphic_fields(%{type: "audio"}), do: [:id, :url, :duration, :bitrate]

      def polymorphic_relationships(%{type: "image"}), do: [album: AlbumView]
      def polymorphic_relationships(%{type: "video"}), do: [playlist: PlaylistView, author: UserView]
      def polymorphic_relationships(%{type: "audio"}), do: [album: AlbumView, artist: ArtistView]
    end

### Required Callbacks for Polymorphic Resources

When using polymorphic resources, you must implement these callbacks instead of their non-polymorphic counterparts:

- `polymorphic_type/1` - Returns the JSONAPI type string based on the data
- `polymorphic_fields/1` - Returns the list of fields to serialize based on the data

### Optional Callbacks for Polymorphic Resources

- `polymorphic_relationships/1` - Returns relationships specific to the data type (defaults to empty list)

### Example Usage

With the above `MediaView`, you can serialize different media types:

    # Image data
    image = %{id: 1, type: "image", url: "/image.jpg", width: 800, height: 600, alt_text: "A photo"}
    MediaView.show(image, conn)
    # => %{data: %{id: "1", type: "image", attributes: %{url: "/image.jpg", width: 800, height: 600, alt_text: "A photo"}}}

    # Video data
    video = %{id: 2, type: "video", url: "/video.mp4", duration: 120, thumbnail: "/thumb.jpg"}
    MediaView.show(video, conn)
    # => %{data: %{id: "2", type: "video", attributes: %{url: "/video.mp4", duration: 120, thumbnail: "/thumb.jpg"}}}

### Custom Field Functions

You can still define custom field functions that work across all polymorphic types:

    defmodule MediaView do
      use JSONAPI.View, polymorphic_resource?: true

      def file_size(data, _conn) do
        # Custom logic to calculate file size
        calculate_file_size(data.url)
      end

      def polymorphic_fields(%{type: "image"}), do: [:id, :url, :file_size, :width, :height]
      def polymorphic_fields(%{type: "video"}), do: [:id, :url, :file_size, :duration]
      # ... other polymorphic implementations
    end

### Notes

- When `polymorphic_resource?: true` is set, the regular `type/0`, `fields/0`, and `relationships/0`
  functions are not used and will return default values (nil or empty list)
- The polymorphic callbacks receive the actual data as their first argument, allowing you to
  determine the appropriate type, fields, and relationships dynamically
- All other view functionality (links, meta, hidden fields, etc.) works the same way
- **Important**: Polymorphic resources currently do not work for deserializing data from POST
  requests yet. They are only supported for serialization (rendering responses)

## Options
  * `:host` (binary) - Allows the `host` to be overridden for generated URLs. Defaults to `host` of the supplied `conn`.

  * `:scheme` (atom) - Enables configuration of the HTTP scheme for generated URLS.  Defaults to `scheme` from the provided `conn`.

  * `:namespace` (binary) - Allows the namespace of a given resource. This may be
    configured globally or overridden on the View itself. Note that if you have
    a globally defined namespace and need to *remove* the namespace for a
    resource, set the namespace to a blank String.

The default behaviour for `host` and `scheme` is to derive it from the `conn` provided, while the
default style for presentation in names is to be underscored and not dashed.

# `data`

```elixir
@type data() :: any()
```

# `field`

```elixir
@type field() :: atom()
```

# `link_object`

```elixir
@type link_object() :: %{:href =&gt; String.t(), optional(:meta) =&gt; meta()}
```

# `links`

```elixir
@type links() :: %{required(atom()) =&gt; String.t() | link_object()}
```

# `meta`

```elixir
@type meta() :: %{required(atom()) =&gt; String.t()}
```

# `options`

```elixir
@type options() :: keyword()
```

# `resource_fields`

```elixir
@type resource_fields() :: [field()]
```

# `resource_id`

```elixir
@type resource_id() :: String.t()
```

# `resource_relationships`

```elixir
@type resource_relationships() :: [
  {atom(), t() | {t(), :include} | {atom(), t()} | {atom(), t(), :include}}
]
```

# `resource_type`

```elixir
@type resource_type() :: String.t()
```

# `t`

```elixir
@type t() :: module()
```

# `attributes`

```elixir
@callback attributes(data(), Plug.Conn.t() | nil) :: map()
```

# `fields`

```elixir
@callback fields() :: resource_fields()
```

# `get_field`
*optional* 

```elixir
@callback get_field(field(), data(), Plug.Conn.t()) :: any()
```

# `hidden`

```elixir
@callback hidden(data()) :: [field()]
```

# `id`

```elixir
@callback id(data()) :: resource_id() | nil
```

# `links`

```elixir
@callback links(data(), Plug.Conn.t()) :: links()
```

# `meta`

```elixir
@callback meta(data(), Plug.Conn.t()) :: meta() | nil
```

# `namespace`

```elixir
@callback namespace() :: String.t()
```

# `pagination_links`

```elixir
@callback pagination_links(
  data(),
  Plug.Conn.t(),
  JSONAPI.Paginator.page(),
  JSONAPI.Paginator.options()
) ::
  JSONAPI.Paginator.links()
```

# `path`

```elixir
@callback path() :: String.t() | nil
```

# `polymorphic_fields`

```elixir
@callback polymorphic_fields(data()) :: resource_fields()
```

# `polymorphic_relationships`

```elixir
@callback polymorphic_relationships(data()) :: resource_relationships()
```

# `polymorphic_type`

```elixir
@callback polymorphic_type(data()) :: resource_type() | nil
```

# `relationships`

```elixir
@callback relationships() :: resource_relationships()
```

# `type`

```elixir
@callback type() :: resource_type() | nil
```

# `url_for`

```elixir
@callback url_for(data(), Plug.Conn.t() | nil) :: String.t()
```

# `url_for_pagination`

```elixir
@callback url_for_pagination(data(), Plug.Conn.t(), JSONAPI.Paginator.params()) ::
  String.t()
```

# `url_for_rel`

```elixir
@callback url_for_rel(term(), String.t(), Plug.Conn.t() | nil) :: String.t()
```

# `visible_fields`

```elixir
@callback visible_fields(data(), Plug.Conn.t() | nil) :: [atom()]
```

# `url_for`

```elixir
@spec url_for(t(), term(), Plug.Conn.t() | nil) :: String.t()
```

# `url_for_pagination`

# `url_for_rel`

```elixir
@spec url_for_rel(t(), data(), resource_type(), Plug.Conn.t() | nil) :: String.t()
@spec url_for_rel(t(), data(), Plug.Conn.query_params(), JSONAPI.Paginator.params()) ::
  String.t()
```

# `visible_fields`

```elixir
@spec visible_fields(t(), data(), Plug.Conn.t() | nil) :: [atom()]
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
