RethinkDB.Changefeed behaviour
A behaviour for implementing RethinkDB change feeds.
The Changefeed
behaviour is a superset of the GenServer
behaviour. It adds some return
values and some callbacks to make handling updates simple.
A very simple example Changefeed:
defmodule PersonFeed do
use RethinkDB.Changefeed
def init(opts) do
id = Dict.get(opts, :id)
db = Dict.get(opts, :db)
query = RethinkDB.Query.table("people")
|> RethinkDB.Query.get(id)
|> RethinkDB.Query.changes
{:subscribe, query, db, nil}
end
def handle_update(%{"new_val" => data}, _) do
{:next, data}
end
def handle_call(:get, _from, data) do
{:reply, data, data}
end
end
The example shows one of many patterns. In this case, we are keeping a local
copy of the record and updating it whenever it changes in the database. Clients
in the application can access the data via Changefeed.call(pid, :get)
.
The same pattern can be used on a sequence:
defmodule TeamFeed do
use RethinkDB.Changefeed
def init(opts) do
name = Dict.get(opts, :name)
team = Dict.get(opts, :team) # team is a map of ids to maps
db = Dict.get(opts, :db)
query = RethinkDB.Query.table("people")
|> RethinkDB.Query.filter(%{team: name})
|> RethinkDB.Query.changes
{:subscribe, query, db, team}
end
def handle_update(data, team) do
team = Enum.reduce(data, team, fn ->
# no `old_val` means a new entry was created
%{"new_val" => val, "old_val" => nil}, acc ->
Dict.put(acc, val["id"], val)
# no `new_val` means an entry was deleted
%{"new_val" => nil, "old_val" => val}, acc ->
Dict.delete(acc, val["id"])
# otherwise, we have an update
%{"new_val" => val}, acc ->
Dict.put(acc, val["id"], val)
end)
{:next, team}
end
def handle_call(:get, _from, data) do
{:reply, data, data}
end
end
A changefeed is designed to handle updates and to update any state associated with the feed. If a publisher subscriber model is desired, a GenEvent can be used in conjunction with a changefeed. Here’s an example:
defmodule EventFeed do
use RethinkDB.Changefeed
def init(opts) do
gen_event = Dict.get(opts, :gen_event)
db = Dict.get(opts, :db)
query = RethinkDB.Query.table("events")
|> RethinkDB.Query.changes
{:subscribe, query, db, gen_event}
end
def handle_update(data, gen_event) do
Enum.each(data, fn
# no `old_val` means a new entry was created
%{"new_val" => val, "old_val" => nil}, acc ->
GenEvent.notify(gen_event,{:create, val})
# no `new_val` means an entry was deleted
%{"new_val" => nil, "old_val" => val}, acc ->
GenEvent.notify(gen_event,{:delete, val})
# otherwise, we have an update
%{"new_val" => val, "old_val" => old_val}, acc ->
GenEvent.notify(gen_event,{:update, old_val, val})
end)
{:next, gen_event}
end
end
Summary
Functions
See GenServer.call/2
See GenServer.call/3
See GenServer.cast/2
Start Changefeed process linked to current process
Callbacks
See GenServer.code_change/3
See GenServer.handle_call/3
See GenServer.handle_cast/2
See GenServer.handle_info/2
Called when new data is received from a feed
Called when process is first started. start_link
blocks until init returns
See GenServer.terminate/2
Functions
Callbacks
Specs
code_change(vsn :: any, state :: any, extra :: any) :: any
See GenServer.code_change/3
Specs
handle_call(request :: any, from :: any, state :: any) :: any
See GenServer.handle_call/3
Specs
handle_cast(request :: any, state :: any) :: any
See GenServer.handle_cast/2
Specs
handle_info(msg :: any, state :: any) :: any
See GenServer.handle_info/2
Specs
handle_update(update :: any, state :: any) :: any
Called when new data is received from a feed.
Expects return to be one of the following:
{:next, state}
- Request the next set of data for the feed from the database.{:stop, reason, state}
- Stops the feed.terminate/2
will be called withreason
andstate
Specs
init(opts :: any) :: any
Called when process is first started. start_link
blocks until init returns.
Expects return to be one of the following:
{:subscribe, query, db, state}
- Upon this returning, start_link will return and immediately a connection will be made to the database and a feed established. If a feed cannot be established then it will be retried with an exponential backoff.{:stop, reason}
- This will cause start_link to return{:error, reason}
and the process will exit with reasonreason