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

call(server, request)

See GenServer.call/2

call(server, request, timeout)

See GenServer.call/3

cast(server, request)

See GenServer.cast/2

start_link(mod, args, opts)

Start Changefeed process linked to current process.

args will be passed into init. opts are standard GenServer options.

Callbacks

code_change(vsn, state, extra)

Specs

code_change(vsn :: any, state :: any, extra :: any) :: any

See GenServer.code_change/3

handle_call(request, from, state)

Specs

handle_call(request :: any, from :: any, state :: any) :: any

See GenServer.handle_call/3

handle_cast(request, state)

Specs

handle_cast(request :: any, state :: any) :: any

See GenServer.handle_cast/2

handle_info(msg, state)

Specs

handle_info(msg :: any, state :: any) :: any

See GenServer.handle_info/2

handle_update(update, state)

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 with reason and state
init(opts)

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 reason reason
terminate(reason, state)

Specs

terminate(reason :: any, state :: any) :: any

See GenServer.terminate/2