# ProcessTreeDictionary
[![Master](https://travis-ci.org/seomoz/process_tree_dictionary.svg?branch=master)](https://travis-ci.org/seomoz/process_tree_dictionary)
[![Hex.pm Version](http://img.shields.io/hexpm/v/process_tree_dictionary.svg?style=flat)](https://hex.pm/packages/process_tree_dictionary)
Implements a dictionary that is scoped to a process tree by replacing
the group leader with a process that:
- Maintains a dictionary of state
- Forwards all unrecognized messages to the original group leader so
that IO still works
Any process can be the root of its own process tree by starting a
`ProcessTreeDictionary`.
The [Erlang docs](http://erlang.org/doc/man/erlang.html#group_leader-0)
provide a summary of what a group leader is:
> Every process is a member of some process group and all groups have a
> group leader. All I/O from the group is channeled to the group leader.
> When a new process is spawned, it gets the same group leader as the
> spawning process.
Since every new process inherits the group leader from its parent, a process
can start a `ProcessTreeDictionary` in place of its existing group leader, and
*every* descendant process will inherit it, allowing them to access the state
of the same `ProcessTreeDictionary`.
Note that _all_ functions provided by this module rely upon side effects.
Since referential transparency is a primary value of Elixir, Erlang, and
functional programming in general, and _none_ of the functions provided
by this module are referentially transparent, we recommend you limit your
usage of this module to specialized situations, such as for building test
fakes to stand-in for stateful modules.
Important caveat: if any processes in your tree start an application with
`Application.start`, `Application.ensure_started`, or
`Application.ensure_all_started`, the started application processes will _not_
be a part of the process tree, because OTP manages application starts for you.
If you need to access the `ProcessTreeDictionary` from the started processes,
you'll need to start the supervisor of the application yourself. For more info,
see the [Erlang docs](http://erlang.org/doc/apps/kernel/application.html#start-1).
## Installation
Add `process_tree_dictionary` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:process_tree_dictionary, "~> 1.0.0"}]
end
```
## Example Usage
At Moz, we use this library to implement test fakes to stand in for
_stateful modules_. A stateful module exports functions that operate
on additional state that is not present in any of the arguments. For
example, consider a theoretical Amazon S3 client for our application
that provides the following interface:
``` elixir
defmodule MyApp.S3 do
def get(bucket, key) do
# get the object at the provided key
end
def put(bucket, key, object) do
# put the object at the provided key
end
end
```
In our test environment, we would like to use an alternate
implementation of this module's interface. Before we built
`ProcessTreeDictionary`, there were two common approaches we
used for building stateful test fakes in this kind of situation:
1. **Using the process dictionary**: in our fake implementations of
`get/2` and `put/3`, we would simply delegate to `Process.get/2`
and `Process.put/2`. This has the advantage of working
correctly for `async: true` tests, but fails if any of the code you
are testing spawns processes and uses your fake S3 module in a
spawned process (since its process dictionary is different).
2. **Using a global agent**: we would start a globally named agent
and then use `Agent.get/2` and `Agent.update/2` to manage the
state. This has the advantage of working correctly for tests
that use the fake S3 module in a spawned process, but is not
compatible with `async: true` tests. Even worse, if you forget
to change `async: true` to `async: false`, it can lead to
flickering tests.
`ProcessTreeDictionary` provides an alternate approach that does not
suffer from these problems:
* Each test defines its own isolated process tree, which allows you
to safely use `ProcessTreeDictionary` in `async: true` tests.
* Since spawned processes belong to the same process tree as their
parent process, tests that spawn processes are supported.
Here's what a fake implementation of our S3 client looks like using
`ProcessTreeDictionary`:
``` elixir
defmodule MyApp.S3.TestFake do
def get(bucket, key) do
key_path(bucket, key)
|> ProcessTreeDictionary.get(:not_found)
|> case do
:not_found -> {:error, :not_found}
object -> {:ok, object}
end
end
def put(bucket, key, object) do
# Start the ProcessTreeDictionary if it's not already started
# so we can write to it.
ProcessTreeDictionary.ensure_started()
key_path(bucket, key)
|> ProcessTreeDictionary.put(object)
end
defp key_path(bucket, key) do
# Scope our dictionary keys using our module name to prevent
# key conflicts with other uses of ProcessTreeDictionary.
[__MODULE__, bucket, key]
end
end
```