Skip to content

Commit

Permalink
Merge pull request #14 from florinpatrascu/v2_dev_branch
Browse files Browse the repository at this point in the history
v2.0.0
  • Loading branch information
florinpatrascu authored Jul 21, 2024
2 parents d8e0c0f + aafc6aa commit aed8a08
Show file tree
Hide file tree
Showing 37 changed files with 502 additions and 1,755 deletions.
12 changes: 4 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ jobs:
matrix:
include:
- pair:
elixir: "1.11"
otp: "22.3"
elixir: "1.15"
otp: "26.2"
postgres: "12.13-alpine"
- pair:
elixir: "1.12"
otp: "22.3"
postgres: "12.13-alpine"
- pair:
elixir: "1.14"
otp: "25.2"
elixir: "1.17"
otp: "26.2"
postgres: "15.1-alpine"
lint: lint

Expand Down
24 changes: 0 additions & 24 deletions .iex.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,3 @@ try do
rescue
Code.LoadError -> :rescued
end

defmodule CTT do
use CTE,
otp_app: :cte,
adapter: CTE.Adapter.Memory,
nodes: %{
1 => %{id: 1, author: "Olie", comment: "Is Closure Table better than the Nested Sets?"},
2 => %{id: 2, author: "Rolie", comment: "It depends. Do you need referential integrity?"},
3 => %{id: 3, author: "Olie", comment: "Yeah."}
},
paths: [[1, 1, 0], [1, 2, 1], [1, 3, 2], [2, 2, 0], [2, 3, 1], [3, 3, 0]]
end

Mix.shell().info([
:green,
"""
A CTT module was defined for you. Start is and use it like this:
iex> CTT.start_link()
iex> {:ok, tree} = CTT.tree(1)
iex> CTE.Utils.print_tree(tree, 1)
iex> CTE.Utils.print_tree(tree,1, callback: &({&2[&1].author <> ":", &2[&1].comment}))
"""
])
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 25.1.2
elixir 1.14.2-otp-25
erlang 26.2.4
elixir 1.15.8-otp-26
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# CHANGELOG

## 2.0.0

This version is introducing major breaking changes. We drop the concept of a CT Adapter and focus on using Ecto, for the core functions. The (in)memory adapter is gone.

Also important: we're going "process-less", simple, streamlined, efficient and maybe a tad fast(er)

This is very much a work in progress, with a list of immediate todos as follow:

- code cleanup and update the documentation
- allow the user to define her own:
- primary key; name and maybe type
- foreign key; name and maybe type - optional
- callbacks (l8r)
- telemetry and better logging
- mix tasks for generating CT migrations
- support for "plugins" ..

## 1.1.5

- dependencies update
Expand Down
167 changes: 131 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,160 @@
[![Hex.pm](https://img.shields.io/hexpm/dt/closure_table.svg?maxAge=2592000)](https://hex.pm/packages/closure_table)
[![Hexdocs.pm](https://img.shields.io/badge/api-hexdocs-brightgreen.svg)](https://hexdocs.pm/closure_table)

> this library provides two adapters: an in-memory one, and one for using the closure-table solution with Ecto; for your testing and development convenience.
The Closure Table solution is a simple and elegant way of storing hierarchies. It involves storing all paths through a tree, not just those with a direct parent-child relationship. You may want to chose this model, over the [Nested Sets model](https://en.wikipedia.org/wiki/Nested_set_model), should you need referential integrity and to assign nodes to multiple trees.

Throughout the various examples and tests, we will refer to the hierarchies depicted below, where we're modeling a hypothetical forum-like discussion between [Rolie, Olie and Polie](https://www.youtube.com/watch?v=LTkmaE_QWMQ), and their debate around the usefulness of this implementation :)

![Closure Table](assets/closure_table.png)

Warning:

> This version is introducing major breaking changes. We drop the concept of a CT Adapter and focus on using Ecto, for the core functions. The (in)memory adapter is gone.

## Quick start

To start, you can simply use one `Adapter` from the ones provided, same way you'd use the Ecto's own Repo:
The current implementation is depending on Ecto ~> 3.1; using [Ecto.SubQuery](https://hexdocs.pm/ecto/Ecto.SubQuery.html)!

For this implementation to work you'll have to provide two tables, and the name of the Repo used by your application:

1. a table name containing the nodes. Having the `id`, as a the primary key. This is the default for now - configurable in the near future
2. a table name where the tree paths will be stores.
3. the name of the Ecto.Repo, defined by your app

In a future version we will provide you with a convenient migration template to help you starting, but for now you must supply these tables.

For example, given you have the following Schemas for comments:

```elixir
defmodule CTT do
use CTE,
otp_app: :cte,
adapter: CTE.Adapter.Memory,
nodes: %{
1 => %{id: 1, author: "Olie", comment: "Is Closure Table better than the Nested Sets?"},
2 => %{id: 2, author: "Rolie", comment: "It depends. Do you need referential integrity?"},
3 => %{id: 3, author: "Olie", comment: "Yeah."}
},
paths: [[1, 1, 0], [1, 2, 1], [1, 3, 2], [2, 2, 0], [2, 3, 1], [3, 3, 0]]
end
defmodule CT.Comment do
use Ecto.Schema
import Ecto.Changeset

@timestamps_opts [type: :utc_datetime]

schema "comments" do
field :text, :string
belongs_to :author, CT.Author

timestamps()
end
end
```

and a table used for storing the parent-child relationships

```elixir

defmodule CT.TreePath do
use Ecto.Schema
import Ecto.Changeset
alias CT.Comment

@primary_key false

schema "tree_paths" do
belongs_to :parent_comment, Comment, foreign_key: :ancestor
belongs_to :comment, Comment, foreign_key: :descendant
field :depth, :integer, default: 0
end
end
```

With the configuration above, the `:nodes` attribute is a map containing the comments our interlocutors made; these are "nodes", in CTE's parlance. When using the `CTE.Adapter.Ecto` implementation, the `:nodes` attribute will be a Schema (or a table name! In our initial implementation, the nodes definitions must have at least the `:id`, as one of their properties. This caveat will be lifted in a later implementation. The `:paths` attribute represents the parent-child relationship between the comments.
we can define the following module:

```elixir

defmodule CT.MyCTE do
use CTE,
repo: CT.Repo,
nodes: CT.Comment,
paths: CT.TreePath
end
```

Add the `CTM` module to your main supervision tree:
We add our CTE Repo to the app's main supervision tree, like this:

```elixir
defmodule CTM.Application do
@moduledoc false
defmodule CT.Application do
use Application

def start(_type, _args) do
children = [
CT.Repo,
]

opts = [strategy: :one_for_one, name: CT.Supervisor]
Supervisor.start_link(children, opts)
end
end
```

use Application
restart your application.

def start(_type, _args) do
opts = [strategy: :one_for_one, name: CTM.Supervisor]
Then using `iex -S mix`, we can start experimenting. Examples:

Supervisor.start_link([CTM], opts)
end
end
```elixir
iex» CT.MyCTE.ancestors(9)
{:ok, [1, 4, 6]}

iex» {:ok, tree} = CT.MyCTE.tree(1)
{:ok,
%{
nodes: %{
6 => %CT.Comment{
__meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
author: #Ecto.Association.NotLoaded<association :author is not loaded>,
author_id: 2,
id: 6,
inserted_at: ~U[2019-07-21 01:10:35Z],
text: "Everything is easier, than with the Nested Sets.",
updated_at: ~U[2019-07-21 01:10:35Z]
},
8 => %CT.Comment{
__meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
author: #Ecto.Association.NotLoaded<association :author is not loaded>,
author_id: 1,
id: 8,
inserted_at: ~U[2019-07-21 01:10:35Z],
text: "I’m sold! And I’ll use its Elixir implementation! <3",
updated_at: ~U[2019-07-21 01:10:35Z]
},
...

},
paths: [
[1, 1, 0],
[1, 2, 1],
[2, 2, 0],
[1, 3, 2],
[2, 3, 1],
[3, 3, 0],
[1, 7, 3],
[2, 7, 2],
...
]
}}
```

And then using `iex -S mix`, for quickly experimenting with the CTE API, let's find the descendants of comment #1:
if you want to visualize a tree, you can do that too:

```elixir
iex» CTM.descendants(1)
{:ok, [3, 2]}
iex> {:ok, tree} = CTT.tree(1)
...
iex> CTE.Utils.print_tree(tree, 1)
...
iex» CTE.Utils.print_tree(tree,1, callback: &({&2[&1].author <> ":", &2[&1].comment}))
iex» CTE.Utils.print_tree(tree, 1, callback: &({&2[&1], &2[&1].text}))
```

Is Closure Table better than the Nested Sets?
└── It depends. Do you need referential integrity?
└── Yeah.
and you may see this:

```txt
Is Closure Table better than the Nested Sets?
├── It depends. Do you need referential integrity?
│ └── Yeah
│ └── Closure Table *has* referential integrity?
└── Querying the data it's easier.
├── What about inserting nodes?
└── Everything is easier, than with the Nested Sets.
├── I'm sold! And I'll use its Elixir implementation! <3
└── w⦿‿⦿t!
```

Please check the docs for more details and return from more updates!
Expand All @@ -82,7 +177,7 @@ by adding `closure_table` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:closure_table, "~> 1.1"}
{:closure_table, "~> 2.0"}
]
end
```
Expand Down
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Mix.Config
import Config

config :mix_test_watch,
clear: true
Expand Down
2 changes: 1 addition & 1 deletion examples/ct_ecto/config/dev.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Config

config :ct, CT.Repo,
database: "ct_ecto_dev",
database: "ct_dev",
username: "postgres",
password: "postgres",
hostname: "localhost",
Expand Down
2 changes: 1 addition & 1 deletion examples/ct_ecto/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ config :logger, :console, format: "[$level] $message\n"
config :logger, :level, :error

config :ct, CT.Repo,
database: "ct_ecto_test",
database: "ct_test",
username: "postgres",
password: "postgres",
hostname: "localhost",
Expand Down
10 changes: 0 additions & 10 deletions examples/ct_ecto/lib/ct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ defmodule CT do
else
e -> {:error, e}
end

# Ecto.Multi.new()
# |> Multi.insert(:comment, cs)
# |> Multi.run(:path, &insert_node/2)
# |> Repo.transaction()
end

@spec reply(Comment.t(), Comment.t()) :: {:ok, Comment.t()} | {:error, any()}
Expand All @@ -39,9 +34,4 @@ defmodule CT do
end

def tree(comment), do: MyCTE.tree(comment.id)

# defp insert_node(_repo, %{comment: comment}) do
# MyCTE.insert(comment.id, comment.id)
# {:ok, [[comment.id, comment.id]]}
# end
end
3 changes: 1 addition & 2 deletions examples/ct_ecto/lib/ct/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ defmodule CT.Application do

def start(_type, _args) do
children = [
CT.Repo,
CT.MyCTE
CT.Repo
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
2 changes: 0 additions & 2 deletions examples/ct_ecto/lib/ct/my_cte.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ defmodule CT.MyCTE do
Comments hierarchy
"""
use CTE,
otp_app: :ct,
adapter: CTE.Adapter.Ecto,
repo: CT.Repo,
nodes: CT.Comment,
paths: CT.TreePath
Expand Down
2 changes: 2 additions & 0 deletions examples/ct_ecto/lib/ct/tree_path.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule CT.TreePath do
use Ecto.Schema

import Ecto.Changeset

alias CT.Comment

@primary_key false
Expand Down
2 changes: 1 addition & 1 deletion examples/ct_ecto/test/ct_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule CTTest do
use CT.DataCase, async: false
use CT.DataCase

describe "Forum" do
setup do
Expand Down
1 change: 0 additions & 1 deletion examples/ct_ecto/test/support/data_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ defmodule CT.DataCase do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(CT.Repo)

unless tags[:async] do
# adapter = CT.MyCTE.__adapter__()
Ecto.Adapters.SQL.Sandbox.mode(CT.Repo, {:shared, self()})
# Ecto.Adapters.SQL.Sandbox.allow(CT.Repo, self(), adapter)
end
Expand Down
4 changes: 0 additions & 4 deletions examples/ct_memory/.formatter.exs

This file was deleted.

Loading

0 comments on commit aed8a08

Please sign in to comment.