Phoenix.Sync (Phoenix.Sync v0.5.1)
View SourceReal-time sync for Postgres-backed Phoenix applications.
See the docs for more information.
Summary
Functions
Interrupts all long-polling requests matching the given shape definition.
Returns the required adapter configuration for your Phoenix Endpoint or
Plug.Router
.
Returns a shape definition for the given params.
Types
@type param_overrides() :: [param_override()]
@type queryable() :: Ecto.Queryable.t() | Ecto.Schema.t() | Ecto.Changeset.t()
@type shape_definition() :: String.t() | queryable() | shape_specification()
Functions
@spec interrupt( shape_definition() | (match_shape_params() -> boolean()), shape_specification() ) :: {:ok, non_neg_integer()}
Interrupts all long-polling requests matching the given shape definition.
The broader the shape definition, the more requests will be interrupted.
Returns the number of interrupted requests.
Examples
To interrupt all shapes on the todos
table:
Phoenix.Sync.interrupt("todos")
Phoenix.Sync.interrupt(table: "todos")
or the same using an Ecto.Schema
module:
Phoenix.Sync.interrupt(Todos.Todo)
all shapes with the given parameterized where clause:
Phoenix.Sync.interrupt(table: "todos", where: "user_id = $1")
or a single shape for the given user:
Phoenix.Sync.interrupt(
from(t in Todos.Todo, where: t.user_id == ^user_id)
)
# or
Phoenix.Sync.interrupt(
table: "todos",
where: "user_id = $1",
params: [user_id]
)
# or
Phoenix.Sync.interrupt(
table: "todos",
where: "user_id = '#{user_id}'"
)
If you want more control over the match, you can pass a function that will
receive a normalized shape definition and should return true
if the active
shape matches.
Phoenix.Sync.interrupt(fn %{table: _, where: _, params: _} = shape ->
shape.table == "todos" &&
shape.where == "user_id = $1" &&
shape.params["0"] == user_id
end)
The normalized shape argument is a map with the following keys:
table
, e.g."todos"
namespace
, e.g."public"
where
, e.g."where user_id = $1"
params
, a map of argument position to argument value, e.g.%{"0" => "true", "1" => "..."}
columns
, e.g.["id", "title"]
All except table
may be nil
.
Interrupting Ecto Query-based Shapes
Be careful when mixing Ecto
query-based shapes with interrupt calls using
hand-written where clauses.
The shape
Phoenix.Sync.Controller.sync_stream(conn, params, fn ->
from(t in Todos.Todo, where: t.user_id == ^user_id)
end)
will not be interrupted by
Phoenix.Sync.interrupt(
table: "todos",
where: "user_id = '#{user_id}'"
)
because the where clause matching is a simple exact string match and Ecto
query
generated where clauses will generally be different from the equivalent
hand-written version. If you want to interrupt a query-based shape you should
use the same query as the interrupt criteria.
Writing interrupts
It's better to be too broad with your interrupt calls than too narrow.
Only clients whose shape definition changes after the interrupt/1
call
will be affected.
Supported options
The more options you give the more specific the interrupt call will be. Only the table name is required.
table
- Required. Interrupts all shapes matching the given table. E.g."todos"
namespace
- The table namespace. E.g."public"
where
- The shape's where clause. Can in be parameterized and will match all shapes with the same where filter irrespective of the parameters (unless provided). E.g."status = $1"
,"completed = true"
columns
- The columns included in the shape. E.g.["id", "title", "completed"]
params
- The values associated with a parameterized where clause. E.g.[true, 1, "alive"]
,%{1 => true}
Returns the required adapter configuration for your Phoenix Endpoint or
Plug.Router
.
Phoenix
Configure your endpoint with the configuration at runtime by passing the
phoenix_sync
configuration to your endpoint in the Application.start/2
callback:
def start(_type, _args) do
children = [
# ...
{MyAppWeb.Endpoint, phoenix_sync: Phoenix.Sync.plug_opts()}
]
end
Plug
Add the configuration to the Plug opts in your server configuration:
children = [
{Bandit, plug: {MyApp.Router, phoenix_sync: Phoenix.Sync.plug_opts()}}
]
Your Plug.Router
must be configured with
copy_opts_to_assign
and you should use
the rele
defmodule MyApp.Router do
use Plug.Router, copy_opts_to_assign: :options
use Phoenix.Sync.Controller
use Phoenix.Sync.Router
plug :match
plug :dispatch
sync "/shapes/todos", Todos.Todo
get "/shapes/user-todos" do
%{"user_id" => user_id} = conn.params
sync_render(conn, from(t in Todos.Todo, where: t.owner_id == ^user_id)
end
end
@spec shape!(shape_definition(), shape_specification()) :: Phoenix.Sync.PredefinedShape.t()
Returns a shape definition for the given params.
Examples
An
Ecto.Schema
module:Phoenix.Sync.shape!(MyPlugApp.Todos.Todo)
An
Ecto
query:Phoenix.Sync.shape!(from(t in Todos.Todo, where: t.owner_id == ^user_id))
A
changeset/1
function which defines the table and columns:Phoenix.Sync.shape!(&Todos.Todo.changeset/1)
A
changeset/1
function plus a where clause:Phoenix.Sync.shape!( &Todos.Todo.changeset/1, where: "completed = false" )
or a parameterized where clause:
Phoenix.Sync.shape!( &Todos.Todo.changeset/1, where: "completed = $1", params: [false] )
A keyword list defining the shape parameters:
Phoenix.Sync.shape!( table: "todos", namespace: "my_app", where: "completed = $1", params: [false] )
Options
When defining a shape via a keyword list, it supports the following options:
:table
(String.t/0
) - Required.:where
- Filter the table according to the where clause. The default value isnil
.:columns
- List of columns to include in the shape. Must include all primary keys. Ifnil
this is equivalent to all columns (SELECT *
) The default value isnil
.:namespace
- The namespace the table belongs to. Ifnil
then Postgres will use whatever schema is the default (usuallypublic
). The default value isnil
.:params
- Values of positional parameters in the where clause. These will substitute$i
placeholder in the where clause. The default value isnil
.:replica
- Modifies the data sent in update and delete change messages.When set to
:full
the entire row will be sent for updates and deletes, not just the changed columns.The default value is
:default
.