Sofa.Doc (sofa v0.5.1)

Copy Markdown View Source

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}

Summary

Functions

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.

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.

delete doc

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

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

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

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

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

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

Creates a new (empty) document

Optimistically write/update doc assuming rev matches

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

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.

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

Types

t()

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

Functions

attach(doc, name, data, opts \\ [])

@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(type)

@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(sofa, doc)

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

delete doc

detach(doc, name)

@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(sofa, doc)

@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?(sofa, doc)

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

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

from_map(m)

@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(sofa, doc, opts \\ [])

@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!(sofa, doc, opts \\ [])

@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(id)

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

Creates a new (empty) document

put(sofa, doc)

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

Optimistically write/update doc assuming rev matches

save(doc, sofa)

@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!(doc, sofa)

@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(doc)

@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}