Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/node repo #513

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 69 additions & 18 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,51 @@ defmodule Radiator.Outline do
|> Repo.get(id)
end

@doc """
Gets all nodes of an episode as a tree.

## Examples

iex> get_node_tree(123)
[%Node{}, %Node{}, ..]
"""
# episode_id = 2
# Radiator.Outline.get_node_tree(episode_id)
def get_node_tree(episode_id) do

node_tree_initial_query =
Node
|> where([n], is_nil(n.parent_id))
|> where([n], n.episode_id == ^episode_id)
|> select([n], %{uuid: n.uuid, content: n.content, parent_id: n.parent_id, prev_id: n.prev_id, level: 0})

node_tree_recursion_query = from outline_node in "outline_nodes",
join: node_tree in "node_tree", on: outline_node.parent_id == node_tree.uuid,
select: [outline_node.uuid, outline_node.content, outline_node.parent_id, outline_node.prev_id, node_tree.level + 1]

node_tree_query =
node_tree_initial_query
|> union_all(^node_tree_recursion_query)

tree =
"node_tree"
|> recursive_ctes(true)
|> with_cte("node_tree", as: ^node_tree_query)
|> select([n], %{uuid: n.uuid, content: n.content, parent_id: n.parent_id, prev_id: n.prev_id, level: n.level})
|> Repo.all()
|> Enum.map(fn %{uuid: uuid, content: content, parent_id: parent_id, prev_id: prev_id, level: level} ->
%Node{uuid: binaray_uuid_to_ecto_uuid(uuid), content: content, parent_id: binaray_uuid_to_ecto_uuid(parent_id), prev_id: binaray_uuid_to_ecto_uuid(prev_id), level: level}
end)
{:ok, tree}
end

defp binaray_uuid_to_ecto_uuid(nil), do: nil
defp binaray_uuid_to_ecto_uuid(uuid) do
Ecto.UUID.load!(uuid)
end



@doc """
Creates a node.

Expand All @@ -76,66 +121,72 @@ defmodule Radiator.Outline do
"""
def create_node(attrs \\ %{}) do
%Node{}
|> Node.changeset(attrs)
|> Node.insert_changeset(attrs)
|> Repo.insert()
|> broadcast_node_action(:insert)
end

def create_node(attrs, %{id: id}) do
%Node{creator_id: id}
|> Node.changeset(attrs)
|> Node.insert_changeset(attrs)
|> Repo.insert()
|> broadcast_node_action(:insert)
end

@doc """
Updates a node.
Updates a nodes content.

## Examples

iex> update_node(node, %{field: new_value})
iex> update_node_content(node, %{content: new_value})
{:ok, %Node{}}

iex> update_node(node, %{field: bad_value})
iex> update_node_content(node, %{content: nil})
{:error, %Ecto.Changeset{}}

"""
def update_node(%Node{} = node, attrs) do
def update_node_content(%Node{} = node, attrs) do
node
|> Node.changeset(attrs)
|> Node.update_content_changeset(attrs)
|> Repo.update()
|> broadcast_node_action(:update)
end

@doc """
Deletes a node.
Moves a nodes to another parent.

## Examples

iex> delete_node(node)
iex> move_node(node, %Node{uuid: new_parent_id})
{:ok, %Node{}}

iex> delete_node(node)
iex> move_node(node, nil)
{:error, %Ecto.Changeset{}}

"""
def delete_node(%Node{} = node) do
def move_node(%Node{} = node, %Node{} = parent_node) do
node
|> Repo.delete()
|> broadcast_node_action(:delete)
|> Node.move_changeset(parent_node)
|> Repo.update()
|> broadcast_node_action(:update)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking node changes.
Deletes a node.

## Examples

iex> change_node(node)
%Ecto.Changeset{data: %Node{}}
iex> delete_node(node)
{:ok, %Node{}}

iex> delete_node(node)
{:error, %Ecto.Changeset{}}

"""
def change_node(%Node{} = node, attrs \\ %{}) do
Node.changeset(node, attrs)
def delete_node(%Node{} = node) do
node
|> Repo.delete()
|> broadcast_node_action(:delete)
end

defp broadcast_node_action({:ok, node}, action) do
Expand Down
64 changes: 49 additions & 15 deletions lib/radiator/outline/node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,72 @@ defmodule Radiator.Outline.Node do
@derive {Jason.Encoder, only: [:uuid, :content, :creator_id, :parent_id, :prev_id]}

@primary_key {:uuid, :binary_id, autogenerate: true}

schema "outline_nodes" do
field :content, :string
field :creator_id, :integer
field :parent_id, Ecto.UUID
field :prev_id, Ecto.UUID
field :level, :integer, virtual: true

belongs_to :episode, Episode

timestamps(type: :utc_datetime)
end

@required_fields [
:content,
:episode_id
]

@optional_fields [
:creator_id,
:parent_id,
:prev_id
]
@doc """
A changeset for inserting a new node
Work in progress. Since we currently ignore the tree structure, there is
no concept for a root node.
Also questionable wether a node really needs a content from beginning. So probably a root
doesnt have a content
Another issue might be we need to create the uuid upfront and pass it here
"""
def insert_changeset(node, attributes) do
node
|> cast(attributes, [:content, :episode_id, :creator_id, :parent_id, :prev_id])
|> update_change(:content, &trim/1)
|> validate_required([:content, :episode_id])
end

@all_fields @optional_fields ++ @required_fields
@doc """
Changeset for moving a node
Only the parent_id is allowed and expected to be changed
"""
def move_changeset(node, new_parent_node) do
node
|> cast(%{parent_id: new_parent_node.uuid}, [:parent_id])
|> validate_parent(new_parent_node)
end

@doc false
def changeset(node, attrs) do
@doc """
Changeset for updating the content of a node
"""
def update_content_changeset(node, attrs) do
node
|> cast(attrs, @all_fields)
|> cast(attrs, [:content])
|> update_change(:content, &trim/1)
|> validate_required(@required_fields)
|> validate_required([:content])
end

defp trim(content) when is_binary(content), do: String.trim(content)
defp trim(content), do: content

defp validate_parent(changeset, nil), do: add_error(changeset, :parent_id, "must not be nil")

defp validate_parent(changeset, parent_node) do
cond do
parent_node.uuid == changeset.data.uuid ->
add_error(changeset, :parent_id, "must not be the same as the node itself")

parent_node.parent_id == changeset.data.uuid ->
add_error(changeset, :parent_id, "node is already parent of the parent node")

parent_node.episode_id != changeset.data.episode_id ->
add_error(changeset, :parent_id, "nodes must be in the same episode")

true ->
changeset
end
end
end
2 changes: 1 addition & 1 deletion lib/radiator_web/live/episode_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ defmodule RadiatorWeb.EpisodeLive.Index do

case Outline.get_node(uuid) do
nil -> nil
node -> Outline.update_node(node, attrs)
node -> Outline.update_node_content(node, attrs)
end

socket
Expand Down
6 changes: 5 additions & 1 deletion priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ alias Radiator.{Accounts, Outline, Podcast}
{:ok, show} =
Podcast.create_show(%{title: "Tech Weekly", network_id: network.id})

{:ok, _episode} =
{:ok, past_episode} =
Podcast.create_episode(%{title: "past episode", show_id: show.id})

{:ok, current_episode} =
Expand Down Expand Up @@ -60,3 +60,7 @@ alias Radiator.{Accounts, Outline, Podcast}
episode_id: current_episode.id,
prev_id: node211.uuid
})


{:ok, past_parent_node} =
Outline.create_node(%{content: "Old Content", episode_id: past_episode.id})
Loading