Skip to content

Commit

Permalink
split outline module
Browse files Browse the repository at this point in the history
	into node_repository and outline
in outline all tree aware functions
  • Loading branch information
electronicbites committed Mar 30, 2024
1 parent 3bbb94d commit 38bb9ae
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 291 deletions.
256 changes: 73 additions & 183 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,80 +6,106 @@ defmodule Radiator.Outline do
import Ecto.Query, warn: false

alias Radiator.Outline.Node
alias Radiator.Outline.NodeRepository
alias Radiator.Repo

def create(attrs \\ %{}, _socket_id \\ nil) do
attrs
|> create_node()
end

def delete(%Node{} = node, _socket_id \\ nil) do
node
|> delete_node()
end

@doc """
Returns the list of nodes.
Inserts a node.
## Examples
iex> list_nodes()
[%Node{}, ...]
"""
def list_nodes do
Node
|> Repo.all()
end
iex> insert_node(%{content: 'foo'}, %Node{} = parent_node, %Node{} = prev_node)
{:ok, %Node{}}
@doc """
Returns the list of nodes for an episode.
iex> insert_node(%{content: value}, %Node{} = parent_node, %Node{parent_id: nil} = prev_node)
{:error, :parent_and_prev_not_consistent}
## Examples
"""
# creates a node and inserts it into the outline tree
# if a parent node is given, the new node will be inserted as a child of the parent node
# if a previous node is given, the new node will be inserted after the previous node
# if no parent is given, the new node will be inserted as a root node
# if no previous node is given, the new node will be inserted as the first child of the parent node
def insert_node(attrs, parent_node \\ nil, prev_node \\ nil) do
Repo.transaction(fn ->
prev_node_id = get_node_id(prev_node)
parent_node_id = get_node_id(parent_node)

iex> list_nodes(123)
[%Node{}, ...]
# find Node which has been previously connected to prev_node
node_to_move =
Node
|> where_prev_node_equals(prev_node_id)
|> where_parent_node_equals(parent_node_id)
|> Repo.one()

"""
with true <- parent_and_prev_consistent?(parent_node, prev_node),
{:ok, node} <- NodeRepository.create_node(attrs),
{:ok, _node_to_move} <- move_node_(node_to_move, nil, node.uuid),
{:ok, node} <- move_node_(node, parent_node_id, prev_node_id) do
node
else
false ->
Repo.rollback("Insert node failed. Parent and prev node are not consistent.")

def list_nodes_by_episode(episode_id) do
Node
|> where([p], p.episode_id == ^episode_id)
|> Repo.all()
{:error, _} ->
Repo.rollback("Insert node failed. Unkown error")
end
end)
end

@doc """
Returns the the number of nodes for an episode
Updates a nodes content.
## Examples
iex> count_nodes_by_episode(123)
3
iex> update_node_content(node, %{content: new_value})
{:ok, %Node{}}
iex> update_node_content(node, %{content: nil})
{:error, %Ecto.Changeset{}}
"""
def count_nodes_by_episode(episode_id) do
episode_id
|> list_nodes_by_episode()
|> Enum.count()
def update_node_content(%Node{} = node, attrs, _socket_id \\ nil) do
node
|> Node.update_content_changeset(attrs)
|> Repo.update()
end

@doc """
Gets a single node.
Raises `Ecto.NoResultsError` if the Node does not exist.
Removes a node from the tree and deletes it from the repository.
Recursivly deletes all children if there are some.
## Examples
iex> get_node!(123)
%Node{}
iex> remove_node(node)
{:ok, %Node{}}
iex> get_node!(456)
** (Ecto.NoResultsError)
iex> remove_node(node)
{:error, %Ecto.Changeset{}}
"""
def get_node!(id) do
Node
|> Repo.get!(id)
def remove_node(%Node{} = node, _socket_id \\ nil) do
next_node =
Node
|> where([n], n.prev_id == ^node.uuid)
|> Repo.one()

prev_node = get_prev_node(node)

if next_node do
next_node
|> Node.move_node_changeset(%{prev_id: get_node_id(prev_node)})
|> Repo.update()
end

# no tail recursion but we dont have too much levels in a tree
node
|> get_all_child_nodes()
|> Enum.each(fn child_node ->
remove_node(child_node)
end)

# finally delete the node itself from the database
NodeRepository.delete_node(node)
end

@doc """
Expand Down Expand Up @@ -115,25 +141,6 @@ defmodule Radiator.Outline do
|> Repo.all()
end

@doc """
Gets a single node.
Returns `nil` if the Node does not exist.
## Examples
iex> get_node(123)
%Node{}
iex> get_node(456)
nil
"""
def get_node(id) do
Node
|> Repo.get(id)
end

@doc """
Gets all nodes of an episode as a tree.
Uses a Common Table Expression (CTE) to recursively query the database.
Expand Down Expand Up @@ -217,68 +224,6 @@ defmodule Radiator.Outline do
{:ok, tree}
end

@doc """
Creates a node.
## Examples
iex> create_node(%{field: value})
{:ok, %Node{}}
iex> create_node(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_node(attrs \\ %{}, _socket_id \\ nil) do
%Node{}
|> Node.insert_changeset(attrs)
|> Repo.insert()
end

@doc """
Inserts a node.
## Examples
iex> insert_node(%{content: 'foo'}, %Node{} = parent_node, %Node{} = prev_node)
{:ok, %Node{}}
iex> insert_node(%{content: value}, %Node{} = parent_node, %Node{parent_id: nil} = prev_node)
{:error, :parent_and_prev_not_consistent}
"""
# creates a node and inserts it into the outline tree
# if a parent node is given, the new node will be inserted as a child of the parent node
# if a previous node is given, the new node will be inserted after the previous node
# if no parent is given, the new node will be inserted as a root node
# if no previous node is given, the new node will be inserted as the first child of the parent node
def insert_node(attrs, parent_node \\ nil, prev_node \\ nil) do
Repo.transaction(fn ->
prev_node_id = get_node_id(prev_node)
parent_node_id = get_node_id(parent_node)

# find Node which has been previously connected to prev_node
node_to_move =
Node
|> where_prev_node_equals(prev_node_id)
|> where_parent_node_equals(parent_node_id)
|> Repo.one()

with true <- parent_and_prev_consistent?(parent_node, prev_node),
{:ok, node} <- create_node(attrs),
{:ok, _node_to_move} <- move_node_(node_to_move, nil, node.uuid),
{:ok, node} <- move_node_(node, parent_node_id, prev_node_id) do
node
else
false ->
Repo.rollback("Insert node failed. Parent and prev node are not consistent.")

{:error, _} ->
Repo.rollback("Insert node failed. Unkown error")
end
end)
end

defp move_node_(nil, _parent_node_id, _prev_node_id), do: {:ok, nil}

defp move_node_(node, parent_node_id, prev_node_id) do
Expand All @@ -303,61 +248,6 @@ defmodule Radiator.Outline do
defp where_parent_node_equals(node, nil), do: where(node, [n], is_nil(n.parent_id))
defp where_parent_node_equals(node, parent_id), do: where(node, [n], n.parent_id == ^parent_id)

@doc """
Updates a nodes content.
## Examples
iex> update_node_content(node, %{content: new_value})
{:ok, %Node{}}
iex> update_node_content(node, %{content: nil})
{:error, %Ecto.Changeset{}}
"""
def update_node_content(%Node{} = node, attrs, _socket_id \\ nil) do
node
|> Node.update_content_changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a node.
## Examples
iex> delete_node(node)
{:ok, %Node{}}
iex> delete_node(node)
{:error, %Ecto.Changeset{}}
"""
def delete_node(%Node{} = node) do
next_node =
Node
|> where([n], n.prev_id == ^node.uuid)
|> Repo.one()

prev_node = get_prev_node(node)

if next_node do
next_node
|> Node.move_node_changeset(%{prev_id: get_node_id(prev_node)})
|> Repo.update()
end

# no tail recursion but we dont have too much levels in a tree
node
|> get_all_child_nodes()
|> Enum.each(fn child_node ->
delete_node(child_node)
end)

node
|> Repo.delete()
end

defp get_node_id(nil), do: nil

defp get_node_id(%Node{} = node) do
Expand Down
2 changes: 1 addition & 1 deletion lib/radiator/outline/event_consumer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule Radiator.Outline.EventConsumer do

defp process_event(%InsertNodeEvent{payload: payload} = _event) do
payload
|> Outline.create_node()
|> Outline.insert_node()
|> handle_insert_result()

# validate
Expand Down
Loading

0 comments on commit 38bb9ae

Please sign in to comment.