# `Sofa.Doc`
[🔗](https://github.com/skunkwerks/sofa/blob/0.5.1/lib/sofa/doc.ex#L1)

Documentation for `Sofa.Doc`, a test-driven idiomatic Apache CouchDB client.

> If the only tool you have is CouchDB, then
> everything looks like `{:ok, :relax}`

Attachments live in `attachments`, a map of name to `%Sofa.Attachment{}`.
`from_map/1` and `to_map/1` convert them to and from CouchDB's
`_attachments` stubs and inline content, so a document built with
`attach/4` saves its attachments in the same request, and a fetched
document keeps the stubs CouchDB needs to retain them on update.

## Examples

    iex> Sofa.Doc.new("smol")
    %Sofa.Doc{attachments: %{}, body: %{}, id: "smol", rev: nil, type: nil}

# `t`

```elixir
@type t() :: %Sofa.Doc{
  attachments: %{optional(String.t()) =&gt; Sofa.Attachment.t()},
  body: map(),
  id: binary(),
  rev: nil | binary(),
  type: atom() | nil
}
```

# `attach`

```elixir
@spec attach(t(), String.t(), binary(), keyword()) :: t()
```

Attaches content to a document, to be saved inline with it. The content
type comes from the name unless given, see `Sofa.Attachment.new/3`.

    Sofa.Doc.new("recipe")
    |> Sofa.Doc.attach("recipe.txt", "1. Cook spaghetti")
    |> Sofa.Doc.attach("photo", photo_bytes, content_type: "image/jpeg")
    |> Sofa.Doc.save(db)

# `coerce_to_elixir_type`

```elixir
@spec coerce_to_elixir_type(String.t()) :: atom()
```

Coerces a CouchDB "type" field to an existing atom. It is assumed that there
will be a related Elixir Module Type of the same name. Elixir prefixes Module
names with Elixir. and then elides this in iex, tests, and elsewhere, but
here we need to make that explicit.

The "user" type is special-cased as it is already present in CouchDB /_users
database.

This function is expected to be paired up with Ecto Schemas to properly manage
the appropriate fields in your document body.

# `delete`

```elixir
@spec delete(Sofa.t(), t()) :: :ok | {:error, any()}
```

delete doc

# `detach`

```elixir
@spec detach(t(), String.t()) :: t()
```

Removes an attachment from a document. CouchDB deletes it when the
document is next saved without its stub.

# `exists`

```elixir
@spec exists(Sofa.t(), String.t()) :: {:error, any()} | {:ok, t()}
```

Check if doc exists via `HEAD /:db/:doc` and returns either:

- `{:error, not_found}` # doc doesn't exist
- `{:ok, %Sofa.Doc{}}` # doc exists and has metadata

# `exists?`

```elixir
@spec exists?(Sofa.t(), String.t()) :: false | true
```

Check if doc exists via `HEAD /:db/:doc` and returns either true or false

# `from_map`

```elixir
@spec from_map(map()) :: t()
```

Converts CouchDB-native JSON-friendly map to internal `%Sofa.Doc{}` format

## Examples

      iex> %{ "_id" => "smol", "_rev" => "1-cute", "yes" => true}
          |> from_map()

      %Sofa.Doc{
        attachments: nil,
        body: %{"yes" => true},
        id: "smol",
        rev: "1-cute",
        type: nil
      }

# `get`

```elixir
@spec get(Sofa.t(), String.t(), keyword()) :: {:error, any()} | t()
```

GET doc and returns standard HTTP status, or the requested doc

- `{:error, :not_found}` # doc doesn't exist, or similar HTTP status
- `%Sofa.Doc{}`          # doc exists and has metadata

## Options

The attachment-related query parameters of the CouchDB document API:

- `attachments: true` fetches attachment content inline, so each
  `%Sofa.Attachment{}` in `attachments` carries its `data`
- `att_encoding_info: true` adds `encoding` and `encoded_length` to the
  stubs of attachments CouchDB stored compressed
- `atts_since: [revs]` fetches content only for attachments added after
  the given revisions, as used when syncing

    Sofa.Doc.get(db, "SpaghettiWithMeatballs", attachments: true)

# `get!`

```elixir
@spec get!(Sofa.t(), String.t(), keyword()) :: t()
```

Bang! version of `get/3`. Returns the document or raises `Sofa.Error`.
Lets a pipeline start from a fetched document.

    Sofa.Doc.get!(db, "SpaghettiWithMeatballs") |> Sofa.Doc.attach("photo.jpg", bytes) |> Sofa.Doc.save!(db)

# `new`

```elixir
@spec new(String.t() | %{id: String.t(), body: map()} | %{id: String.t()}) :: t()
```

Creates a new (empty) document

# `put`

```elixir
@spec put(Sofa.t(), t()) :: {:ok, t()} | {:error, any()}
```

Optimistically write/update doc assuming rev matches

# `save`

```elixir
@spec save(t(), Sofa.t()) :: {:ok, t()} | {:error, any()}
```

Saves a document, with the document first so it can end a pipeline that
builds it. The same as `put/2` otherwise.

    Sofa.Doc.new(%{id: "smol", body: %{"cute" => true}}) |> Sofa.Doc.save(db)
    #=> {:ok, %Sofa.Doc{id: "smol", rev: "1-..."}}

# `save!`

```elixir
@spec save!(t(), Sofa.t()) :: t()
```

Bang! version of `save/2`. Returns the saved document, with its new rev,
or raises `Sofa.Error`. Lets a pipeline carry on with the saved document.

    doc = Sofa.Doc.new("smol") |> Sofa.Doc.save!(db)

# `to_map`

```elixir
@spec to_map(t()) :: map()
```

Converts internal `%Sofa.Doc{}` format to CouchDB-native JSON-friendly map

## Examples

      iex> %Sofa.Doc{id: "smol", rev: "1-cute", body: %{"yes" => true}}
        |> to_map()

      %{ "_id" => "smol", "_rev" => "1-cute", "yes" => true}

---

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