[go: up one dir, main page]

Skip to content

Tree structure & hierarchy for ecto models with ltree(Postgres)

License

Notifications You must be signed in to change notification settings

Byzanteam-Labs/hierarch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hierarch

Elixir CI Hex.pm Hex docs

Hierarch helps you to build tree structure(hierarchy) for ecto models with ltree(Postgres).

Installation

Add hierarch to your list of dependencies in mix.exs:

def deps do
  [
    {:hierarch, "~> 0.2.1"}
  ]
end

Enable ltree extension:

execute "CREATE EXTENSION IF NOT EXISTS ltree"

Add GIST index:

create index(:catelogs, [:path], using: "GIST")

Example

Set types at config/config.exs or your environment config file

config :my_app, MyApp.Repo,
  adapter: Ecto.Adapters.Postgres,
  types: Hierarch.Postgrex.Types

Write a migration for this functionality

defmodule MyApp.Repo.Migrations.CreateCatelogs do
  use Ecto.Migration

  def change do
    execute "CREATE EXTENSION IF NOT EXISTS ltree" # Enables Ltree action

    create table(:catelogs, primary_key: false) do
      add :id, :uuid, primary_key: true # the primary key is UUID
      add :name, :string
      add :path, :ltree

      timestamps()
    end

    create index(:catelogs, [:path], using: "GIST") # Add GIST index to query
  end
end

Use Hierarch in your schema

Options:

  • path_column (default: :path): the name of the database column which stores hierarchy data;
defmodule MyApp.Catelog do
  use Ecto.Schema
  use Hierarch

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

  schema "catelogs" do
    field :name, :string
    field :path, Hierarch.Ecto.UUIDLTree # Set to `UUIDLTree` if the path is ltree type

    timestamps()
  end
end

🔢Use Hierarch with bigint or integer primary key, and custom path_column

defmodule MyApp.Repo.Migrations.CreateOrganizations do
  use Ecto.Migration

  def change do
    execute "CREATE EXTENSION IF NOT EXISTS ltree" # Enables Ltree action

    create table(:organizations) do
      add :name, :string
      add :ancestry, :ltree, null: false, default: ""
    end

    create index(:organizations, [:ancestry], using: "GIST")
  end
end
defmodule MyApp.Organization do
  @moduledoc false
  use Ecto.Schema
  use Hierarch, path_column: :ancestry # set path_column

  schema "organizations" do
    field :name, :string
    field :ancestry, Hierarch.Ecto.LTree # Use LTree for bigint or integer primary key
  end
end

Usage

build_child_of/2

Take the parent struct and attributes struct, return a child struct.

parent = %Catelog{
  id: "570526aa-e2f3-49a7-870a-c150d3bf6ac9",
  name: "Top",
  path: ""
}
catelog = Catelog.build_child_of(parent, %{name: "Top.Science"})
#  %Catelog{
#    id: nil,
#    name: "Top.Science",
#    path: "570526aa-e2f3-49a7-870a-c150d3bf6ac9"
#  }

is_root?/1

Detect a struct whether a root.

catelog = %Catelog{
  id: "570526aa-e2f3-49a7-870a-c150d3bf6ac9",
  name: "Top",
  path: ""
}
Catelog.is_root?(catelog) # true

parent/1

Return the parent query expression of the given struct, return nil if it is the root.

catelog = %Catelog{
  id: "570526aa-e2f3-49a7-870a-c150d3bf6ac9",
  name: "Top",
  path: ""
}
Catelog.parent(catelog) |> Repo.one # nil

root/1

Return the root query expression of the given struct, return itself if it is the root.

catelog = %Catelog{
  id: "570526aa-e2f3-49a7-870a-c150d3bf6ac9",
  name: "Top",
  path: ""
}
Catelog.root(catelog) |> Repo.one # return itself `catelog`

ancestors/2

Return the ancestors query expression of the given struct. Options:

  • :with_self - when true to include itself. Defaults to false
catelog = %Catelog{
  id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
  name: "Top.Hobbies",
  path: "a9ae8f40-b016-4bf9-8224-e2755466e699",
}
Catelog.ancestors(catelog) |> Repo.all
#  [%Catelog{
#    id: "a9ae8f40-b016-4bf9-8224-e2755466e699",
#    name: "Top",
#    path: ""
#  }]

Catelog.ancestors(catelog, with_self: true) |> Repo.all
#  [
#    %Catelog{
#      id: "a9ae8f40-b016-4bf9-8224-e2755466e699",
#      name: "Top",
#      path: ""
#    },
#    %Catelog{
#      id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
#      name: "Top.Hobbies",
#      path: "a9ae8f40-b016-4bf9-8224-e2755466e699"
#    }
#  ]

descendants/2

Return the descendants query expression of the given struct. Options:

  • :with_self - when true to include itself. Defaults to false
catelog = %Catelog{
  id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
  name: "Top.Hobbies",
  path: "a9ae8f40-b016-4bf9-8224-e2755466e699",
}
Catelog.descendants(catelog) |> Repo.all
#  [
#    %Catelog{
#      id: "6ff8db2e-5c01-4e82-a25b-4c1568df1efb",
#      name: "Top.Hobbies.Amateurs_Astronomy",
#      path: "a9ae8f40-b016-4bf9-8224-e2755466e699.06a84054-8827-42c2-9b75-25ed75e6d5f8"
#    }
#  ]

siblings/2

Return the siblings query expression of the given struct. Options:

  • :with_self - when true to include itself. Defaults to false
catelog = %Catelog{
  id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
  name: "Top.Hobbies",
  path: "a9ae8f40-b016-4bf9-8224-e2755466e699",
}
Catelog.siblings(catelog) |> Repo.all
#  [
#    %Catelog{
#      id: "6c11f83f-3c3c-44bf-9940-8153c1f04de9",
#      name: "Top.Science",
#      path: "a9ae8f40-b016-4bf9-8224-e2755466e699"
#    },
#    %Catelog{
#      id: "570526aa-e2f3-49a7-870a-c150d3bf6ac9",
#      name: "Top.Collections",
#      path: "a9ae8f40-b016-4bf9-8224-e2755466e699"
#    }
#  ]

roots/0

Return the roots query expression.

Catelog.roots() |> Repo.all
#  [
#    %Catelog{
#      id: "a9ae8f40-b016-4bf9-8224-e2755466e699",
#      name: "Top",
#      path: ""
#    }
#  ]

Contributing

First, set appropriate settings for test database.

export POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres MIX_ENV=test

docker-compose up

mix do ecto.create, ecto.migrate

run test.

mix test