View Source EctoFoundationDB.Sync (Ecto.Adapters.FoundationDB v0.6.1)

This module defines some conventions for integrating with Phoenix LiveView or any other stateful process. Via EctoFoundationDB watches, your application can automatically be kept up-to-date with changes to the database.

Simply call one of the provided sync* functions in mount/3 or handle_params/3, and this module will do the following:

  1. Read from the database and create necessary watches
  2. Call Phoenix.Component.assign/2, using the provided label
  3. Call Phoenix.LiveView.attach_hook/4 to set up a callback as a hook

Then, upon receiving a watch-ready message, the hook calls Phoenix.Component.assign/2 again with the updated data, and creates new watches as needed.

Socket requirements:

  • The tenant must be stored in the :private field of the provided socket.
  • The sync functions will store a key called :ecto_fdb_sync_data in the :private field.

Examples

These are quick, short examples. Please see Sync Engine III for end-to-end detail.

Quick example 1: Syncing a single record

Suppose you have a LiveView that displays a single user. You can use sync_one/5 to automatically update the user whenever it is created, updated, or deleted.

defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    user_id = "1"
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_one(Repo, User, :user, user_id)}
  end
end

Quick example 2: Syncing a list of records

Suppose you have a LiveView that displays a list of users. You can use sync_all/4 to automatically update the list whenever a user is created, updated, or deleted.

You must have already defined a SchemaMetadata index for the User schema for sync_all/4 to work.

defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_all(Repo, User)}
  end
end

Quick example 3: Syncing a group of records indivudually

Suppose your page displays serveral records simulataneously, but you wish to subscribe to change individually. You can use sync_many/6. This integrates nicely with LiveComponents.

defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    user_ids = ["1", "2", "3"]
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_many(Repo, User, :users, user_ids)}
  end
end

Labels

The label argument is used to identify the data being synced. The label is used as the key in the assigns map. It can be any term. Usually, you'll use an atom for compatibility with Phoenix.

For example, you can provide the label :user and your assigns map will look like this:

iex> assigns
%{user: %User{
  id: 1,
  name: "Alice",
  email: "alice@example.com"
}}

Limitations

The sync* functions themselves define FDB transactions. Therefore, if your desired query is more complex, or if you need to sync multiple collections with internal consistency, you'll need to write your own logic to create and listen for watches. In these more sophisticated cases, we encourage you to avoid the Sync module entirely, and instead handle the {reference(), :ready} messages yourself.

Summary

Functions

Attaches a callback to the :handle_assigns event.

Cancels syncing for the provided label and, if none are left, detaches the hook.

Cancels all syncing and detaches the hook.

Detaches a callback from the :handle_assigns event.

This hook can be attached to a compatible Elixir process to automatically process handle_info :ready messages from EctoFDB.

Initializes a Sync of one or more queryables.

Sets up syncing for a list of records in the database.

Sets up syncing for a list of records in the database, constrained by an indexed field.

Sets up syncing for a list of records in the database, with indivudual watches.

Sets up syncing for a single record in the database.

Functions

Link to this function

assign_map(assigns, std_assigns, idlist_assigns, opts \\ [])

View Source
Link to this function

attach_callback(state, repo, name \\ :default, event, cb, opts \\ [])

View Source

Attaches a callback to the :handle_assigns event.

The callback will be called when the Sync module changes your assigns.

Arguments

  • state: A map with key :assigns and :private. private must be a map with key :tenant
  • repo: An Ecto repository
  • name: The name of the callback. Defaults to :default.
  • event: The event to attach the callback to. Only :handle_assigns is supported.
  • cb: The callback function. Arity must be 2, with the first argument being the state and the second being a map with the old assigns.
  • opts: Options

Options

  • :replace: A boolean indicating whether to replace an existing callback with the same name and event. Defaults to false.
Link to this function

cancel(state, repo, label, opts \\ [])

View Source

Cancels syncing for the provided label and, if none are left, detaches the hook.

Refer to cancel_all/3 for a discussion on when and why to cancel.

Arguments

  • state: A map with key :assigns and :private. private must be a map with key :tenant
  • repo: An Ecto repository
  • label: A label to cancel syncing for
  • opts: Options

Options

  • assign: A boolean indicating whether or not to assign the label to nil or []. Defaults to true.
  • detach_container_hook: A function that takes state, name, repo, opts and modifies state as needed to detach a container hook. When not provided: if Phoenix.LiveView is available, we use Phoenix.LiewView.detach_hook/3, otherwise we do nothing.

Return

Returns an updated state, with :private updated with the following values:

private

  • We cancel and clear the futures in :ecto_fdb_sync_data.
Link to this function

cancel_all(state, repo, opts \\ [])

View Source

Cancels all syncing and detaches the hook.

Canceling syncing is optional. EctoFoundationDB will automatically clean up watches when your process exits.

Arguments

  • state: A map with key :assigns and :private. private must be a map with key :tenant
  • repo: An Ecto repository
  • opts: Options

Options

  • detach_container_hook: A function that takes state, name, repo, opts and modifies state as needed to detach a container hook. When not provided: if Phoenix.LiveView is available, we use Phoenix.LiewView.detach_hook/3, otherwise we do nothing.

Return

Returns an updated state, with :private updated with the following values:

private

  • We cancel and clear the futures in :ecto_fdb_sync_data.
Link to this function

detach_callback(state, repo, name \\ :default, event, opts \\ [])

View Source

Detaches a callback from the :handle_assigns event.

Link to this function

handle_ready(repo, info, state, opts \\ [])

View Source

This hook can be attached to a compatible Elixir process to automatically process handle_info :ready messages from EctoFDB.

This hook is designed to be used with LiveView's attach_hook. If you're using one of the sync_* function in this module along with LiveView, the hook is attached automatically. You do not need to call this function.

Arguments

  • repo: An Ecto repository.
  • info: A message received on the process mailbox. We will inspect messages of the form {ref, :ready} when is_reference(ref), and ignore all others (returning {:cont, state}). Or a list of such messages.
  • state: A map with key :assigns and :private. private must be a map with keys :tenant and :ecto_fdb_sync_data.
  • opts: Options

Options

  • assign: A function that takes the current socket and new assigns and returns a tuple of new assigns and state. By default, we simply update the assigns map with the new labels. The default is not sufficient for LiveView's assign

Result behavior

Either {:cont, state} or {:halt, state} is returned.

  • :cont: Returned when the message was not processed by the Repo.
  • :halt: Returned when the ready message is relevant to the provided futures. The assigns and private are updated accordingly based on the label provided to the matching future. The watches are re-initialized so that the expected syncing behavior will continue.
Link to this function

sync(state, repo, queryable_assigns, opts \\ [])

View Source

Initializes a Sync of one or more queryables.

This is to be paired with handle_ready/3 to provide automatic updating of assigns in state. If you're using the default LiveView attach_hook as described in the Options, then handle_ready/3 will be set up for you automatically.

Arguments

  • state: A map with key :assigns and :private. private must be a map with key :tenant
  • repo: An Ecto repository
  • queryable_assigns: A list of All, One, or Many structs
  • opts: Options

Options

  • assign: A function that takes the current socket and new assigns and returns the updated state. When not provided: if Phoenix.Component is available, we use Phoenix.Component.assign/3, otherwise we use Map.put/3.
  • attach_container_hook: A function that takes state, name, repo, opts and modifies state as needed to attach a hook. When not provided: if Phoenix.LiveView is available, we use Phoenix.LiewView.attach_hook/4, otherwise we do nothing.

Return

Returns an updated state, with :assigns and :private updated with the following values:

assigns

  • Provided labels from queryable_assigns are used to register the results from the database.

private

  • We add or append to the :ecto_fdb_sync_data as needed for internal purposes.
Link to this function

sync_all(state, repo, label, queryable, opts \\ [])

View Source

Sets up syncing for a list of records in the database.

A watch is created for changes to the list with SchemaMetadata.

See sync/4 for more.

Options

  • watch_action: An atom representing the signal from the SchemaMetadata you're interested in syncing. Defaults to :changes
Link to this function

sync_all_by(state, repo, label, queryable, by, opts \\ [])

View Source

Sets up syncing for a list of records in the database, constrained by an indexed field.

A watch is created for changes to the list with SchemaMetadata.

See sync/4 for more.

Options

  • watch_action: An atom representing the signal from the SchemaMetadata you're interested in syncing. Defaults to :changes
Link to this function

sync_many(state, repo, label, schema, ids, opts \\ [])

View Source

Sets up syncing for a list of records in the database, with indivudual watches.

See sync/4 for more.

Link to this function

sync_one(state, repo, label, schema, id, opts \\ [])

View Source

Sets up syncing for a single record in the database.

See sync/4 for more.

Link to this function

watching?(state, repo, label)

View Source