README.md

EctoXml
===========

[![CI](https://github.com/pedro-lb/ecto_xml/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/pedro-lb/ecto_xml/actions/workflows/build_and_test.yml)
[![Module Version](https://img.shields.io/hexpm/v/ecto_xml.svg)](https://hex.pm/packages/ecto_xml)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/ecto_xml/)
[![Total Download](https://img.shields.io/hexpm/dt/ecto_xml.svg)](https://hex.pm/packages/ecto_xml)
[![License](https://img.shields.io/hexpm/l/ecto_xml.svg)](https://github.com/pedro-lb/ecto_xml/blob/master/LICENSE)
[![codecov](https://codecov.io/gh/pedro-lb/ecto_xml/branch/master/graph/badge.svg)](https://codecov.io/gh/pedro-lb/ecto_xml)
[![Last Updated](https://img.shields.io/github/last-commit/pedro-lb/ecto_xml.svg)](https://github.com/pedro-lb/ecto_xml/commits/master)

## Overview

EctoXml provides a way to easily generate XML documents from Ecto Schemas and maps.

It supports converting all ecto schema properties to XML, such as `embeds_one`, `has_one`, `embeds_many` and `has_many`. On top of that,
it also allows customizing name and value resolvers, giving you full control over your XML.

Under the hood it uses [Joshua Nussbaum](https://github.com/joshnuss) amazing work on [xml_builder](https://github.com/joshnuss/xml_builder),
so the idea here is to remove complexity and drying the process of generating XML based on maps and ecto schemas.

Check the examples below to see some code and get started!

## Installation

The package is [available in Hex](https://hex.pm/packages/xml_builder), and can be installed
by adding `ecto_xml` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ecto_xml, "~> 0.1.0"}
  ]
end
```

## Examples

### Available functions

EctoXml exports `to_xml/3` and `to_partial_xml/2`.

The difference is that `to_xml/3` allows us to build a full XML document with a root element whose name is passed as an atom.

The `to_partial_xml/2` function only generates the partial XML element, without the XML document and root element.

The last argument for both functions is always the `options` argument - it is optional and is the same as
[xml_builder options](https://hexdocs.pm/xml_builder/XmlBuilder.html#generate/2).

#### to_partial_xml/2

```elixir
%{name: "Foo Bar"} |> EctoXml.to_partial_xml(format: :none)
```

Produces:

```xml
<name>Foo Bar</name>
```

#### to_xml/3

```elixir
%{name: "Foo Bar"} |> EctoXml.to_xml(:person, format: :none)
```

Produces:

```xml
<?xml version="1.0" encoding="UTF-8"?><person><name>Foo Bar</name></person>
```

### Using Maps

The simplest form of usage is using maps.

```elixir
%{name: "Foo Bar"} |> EctoXml.to_xml(:person)
```

Results in:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<person>
  <name>Foo Bar</name>
</person>
```

### Using Ecto Schemas

We can also use any ecto schema (embedded or not) to generate our XML documents.

```elixir
  defmodule Person do
    use Ecto.Schema

    @primary_key false

    embedded_schema do
      field :name, :string
    end
  end

  %Person{name: "Foo Bar"} |> EctoXml.to_xml(:person)
```

Results in:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<person>
  <name>Foo Bar</name>
</person>
```

### Customizing element names

We can change any element name for the ecto schema being serialized by deriving from the `EctoXml.Builder` protocol.

Here, we have two optional properties:
- `map_field_names`: changes the element name for any field
- `map_array_names`: changes the element name for array fields

Note that in the case of arrays, the `map_field_names` option will not be applied to the array itself. Instead,
it will be applied to the array items, allowing you to customize each item name. In order to change the array
element name, we must use `map_array_names`.

```elixir
  defmodule Post do
    use Ecto.Schema

    @primary_key false

    @derive {
      EctoXml.Builder,
      map_field_names: %{
        :title => :custom_title,
        :comments => :a_comment
      },
      map_array_names: %{
        :comments => :custom_comments
      }
    }

    embedded_schema do
      field :title, :string
      embeds_many :comments, Comment
    end
  end

  defmodule Comment do
    use Ecto.Schema

    @primary_key false

    embedded_schema do
      field :content, :string
    end
  end

  %Post{
    title: "Foo Bar",
    comments: [
      %Comment{content: "Comment 1"},
      %Comment{content: "Comment 2"},
    ]
  }
  |> EctoXml.to_xml(:post)
```

Results in:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<post>
  <custom_comments>
    <a_comment>
      <content>Comment 1</content>
    </a_comment>
    <a_comment>
      <content>Comment 2</content>
    </a_comment>
  </custom_comments>
  <custom_title>Foo Bar</custom_title>
</post>
```

### Customizing element values

We can use the `EctoXml.ValueResolver` protocol to customize and resolve the value for any element type.

It receives some options to resolve the value:
- `value`: the original element value
- `key`: the field name
- `base_module`: the base module which is being serialized to XML

Here's one example for a tuple:

```elixir
defimpl EctoXml.ValueResolver, for: Tuple do
  @moduledoc "ValueResolver implementation for Tuple that for some mysterious reason, always returns 42."

  def resolve(_value, _key, _base_module) do
    "42"
  end
end

%{value: {:ok, "foo bar"}}
|> EctoXml.to_xml(:tuple)
```

Produces:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<tuple>
  <value>42</value>
</tuple>
```

## License

This source code is licensed under the [MIT License](https://github.com/pedro-lb/ecto_xml/blob/master/LICENSE).
Copyright (c) 2021-present, Pedro Bini. All rights reserved.