Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

CURD Rice ๐Ÿš :: for ricing CRUD

docs/crudrice.livemd

CURD Rice ๐Ÿš :: for ricing CRUD

Sheaf ๐ŸŒพ Manipulation

defmodule R do
  def recompile() do
    Mix.Task.reenable("app.start")
    Mix.Task.reenable("compile")
    Mix.Task.reenable("compile.all")
    compilers = Mix.compilers()
    Enum.each(compilers, &Mix.Task.reenable("compile.#{&1}"))
    Mix.Task.run("compile.all")
  end
end

R.recompile()

CRUD Helper โ€“ set up a family of sheafs for discussions UI

R.recompile()
alias Vyasa.Sangh
alias Vyasa.Sangh.Sheaf

CRUD Helper Module

This has a purge function and a seed function. Intent is to use this during the UI creation phase for discussions.

R.recompile()
defmodule CRUDHelper do
  alias Vyasa.Sangh
  alias Vyasa.Sangh.Sheaf
  alias EctoLtree

  @roots [
    {"Hey there, this is the start of a discussion about how to set up the discussions UI",
     "freebird"},
    {"Hello folks, let's talk about why fruits are absolutely the best thing on earth.",
     "fruitfinder"},
    {"I like cars, what about y'all?", "carboi"}
  ]

  @second_layer [
    {"What do you think about the future of discussions?", "discussion_enthusiast"},
    {"Fruits are not just tasty; they're also healthy!", "health_freak"},
    {"Cars have changed so much over the years, haven't they?", "car_historian"},
    {"How do you feel about online discussions?", "digital_native"},
    {"What's your favorite fruit and why?", "fruit_lover"},
    {"What car model do you dream of owning?", "car_dreamer"},
    {"What features do you think discussions should have?", "feature_fanatic"},
    {"Do you prefer sweet or savory fruits?", "taste_explorer"},
    {"What was your first car experience like?", "first_driver"}
  ]

  @third_layer [
    # For first root's children
    [
      {"I think discussions should be more interactive.", "interactivity_lover"},
      {"Have you tried any new discussion platforms?", "platform_explorer"},
      {"What would improve online discussions for you?", "improvement_seeker"}
    ],
    # For second root's children
    [
      {"I love fruits that are both sweet and tangy!", "sweet_tangy_lover"},
      {"Have you ever tried exotic fruits?", "exotic_fruit_explorer"},
      {"What's your go-to fruit for a snack?", "snack_time_fruit"}
    ],
    # For third root's children
    [
      {"Electric cars are the future!", "electric_car_enthusiast"},
      {"What's your opinion on self-driving cars?", "self_driving_advocate"},
      {"Car maintenance tips anyone?", "maintenance_master"}
    ]
  ]

  @doc """
  Seeds a family of sheafs. With 3 roots, 3 children per root and 3 children per 2nd level root.
  """
  def seed_family(sangh_session_id) do
    # Create root sheafs
    created_root_sheafs =
      @roots
      |> Enum.map(fn {body, user_signature} ->
        payload = %{
          id: Ecto.UUID.generate(),
          active: false,
          body: body,
          marks: [],
          traits: ["published"],
          session_id: sangh_session_id,
          signature: user_signature,
          # Each root has 3 children
          child_count: length(@second_layer)
        }

        case Sangh.create_sheaf(payload) do
          {:ok, sheaf} -> sheaf
          {:error, reason} -> IO.puts("Error creating root sheaf: #{reason}")
        end
      end)

    # Create second layer sheafs
    created_second_layer_sheafs =
      created_root_sheafs
      |> Enum.flat_map(fn root ->
        Enum.map(@second_layer, fn {body, user_signature} ->
          payload = %{
            id: Ecto.UUID.generate(),
            active: false,
            body: body,
            marks: [],
            traits: ["published"],
            session_id: sangh_session_id,
            signature: user_signature,
            # Each second layer has 3 children
            child_count: length(@third_layer)
          }

          case Sangh.create_child_sheaf_from_parent(root, payload) do
            {:ok, child_sheaf} -> child_sheaf
            {:error, reason} -> IO.puts("Error creating second layer sheaf: #{reason}")
          end
        end)
      end)

    # Create third layer sheafs
    _created_third_layer_sheafs =
      created_second_layer_sheafs
      |> Enum.flat_map(fn child ->
        Enum.flat_map(@third_layer, fn layer ->
          Enum.map(layer, fn {body, user_signature} ->
            payload = %{
              id: Ecto.UUID.generate(),
              active: false,
              body: body,
              marks: [],
              traits: ["published"],
              session_id: sangh_session_id,
              signature: user_signature,
              # Third layer has no children
              child_count: 0
            }

            case Sangh.create_child_sheaf_from_parent(child, payload) do
              {:ok, grandchild_sheaf} -> grandchild_sheaf
              {:error, reason} -> IO.puts("Error creating third layer sheaf: #{reason}")
            end
          end)
        end)
      end)

    # insert test map to one of the roots:
    updated_root_sheaf_with_mark = hd(created_root_sheafs) |> Sangh.update_sheaf(%{marks: [Stubs.Vyasa.Sangh.Mark.get_dummy_mark()]}) 
    IO.inspect(updated_root_sheaf_with_mark, label: "CHECK ME OUT")
  end

  @doc """
  Indiscriminately purges all the sheafs in the session. 
  Deletes child sheafs before deleting their parents to avoid foreign key constraint errors.
  Handles stale entries gracefully.
  """
  def purge(sangh_session_id) do
    # Retrieve all root sheafs for the session
    root_sheafs = Sangh.get_root_sheafs_by_session(sangh_session_id) |> Vyasa.Repo.preload(:marks)
    # _deleted_marks = root_sheafs |> Enum.map(fn s -> s |> Sangh.delete_marks_in_sheaf() end)


    _second_level_deletes =
      root_sheafs
      |> Enum.map(fn root ->
        Sangh.get_child_sheafs_by_session(sangh_session_id, Sheaf.encode_path(root.id), 2)
      end)
      |> Enum.map(fn sheafs -> sheafs |> Enum.map(fn s -> Sangh.delete_sheaf(s) end) end)

    _first_level_deletes =
      root_sheafs
      |> Enum.map(fn root ->
        Sangh.get_child_sheafs_by_session(sangh_session_id, Sheaf.encode_path(root.id), 1)
      end)
      |> Enum.map(fn sheafs -> sheafs |> Enum.map(fn s -> Sangh.delete_sheaf(s) end) end)

    _root_level_deletes =
      root_sheafs
      |> Enum.map(fn root ->
        Sangh.get_child_sheafs_by_session(sangh_session_id, Sheaf.encode_path(root.id), 0)
      end)
      |> Enum.map(fn sheafs -> sheafs |> Enum.map(fn s -> Sangh.delete_sheaf(s) end) end)
  end
end

Creating the Family Tree

This is for some basic setup while creating the discussion view.

# set based on current session:
sangh_session_id = "164eb05d-221a-4939-b180-8394e1a5515f"
sangh_session_id = "907c4d5e-77a1-4c51-96d1-185b8f314fde"
sangh_session_id = "907c4d5e-77a1-4c51-96d1-185b8f314fde"
CRUDHelper.purge(sangh_session_id)
CRUDHelper.seed_family(sangh_session_id)

Verifying the Family Fixture

root =  hd(Sangh.get_root_sheafs_by_session(sangh_session_id))
Sangh.get_child_sheafs_by_session(sangh_session_id, Sheaf.encode_path(root.id), 2)
|>Enum.count()

%{data: root_sheafs} = Sangh.get_root_sheafs_by_session(sangh_session_id, 1)
root_sheafs

Sheaf Lattice Definition

So this is how we define a flatmap (which we call :kv_verses), so our intent is to use a similar pattern for keeping state in the discussions mode

alias Vyasa.Sangh
alias Vyasa.Sangh.Sheaf
alias Vyasa.Written.{Source}
alias Vyasa.Written
default_lang = "en"
source_title = "hanuman_chalisa"
chap_no = 1
%Source{id: sid} = source = Written.get_source_by_title(source_title)
chaps = Written.list_chapters_by_source(sid, default_lang)
%{verses: verses, translations: [ts | _], title: chap_title, body: chap_body} = chap = Written.get_chapter(chap_no, sid, default_lang)


Enum.into(verses, %{}, &{&1.id, &1})
# Written.get_chapter(1, sid, @default_lang)

Defining Lattices for Discussion Context

So we will be keeping a flatmap for state. First about the sheaf states themselves.

These will be streams of their own:

A) sheafs stream: [ sheaf path label slugs ] => %Sheaf{}

B) sheaf_ui stream [ sheaf id ] => sheaf ui state

  • this will have markโ€™s info for them also ==> for now we shall also load all the marks per sheaf

now for ui state: the Marks UI state looks like this:

%VyasaWeb.Context.Components.UiState.Marks{ is_expanded_view?: false, show_sheaf_modal?: false, is_editable_marks?: false, mark_id_to_ui: %{} }

for sheaf ui do a composition: so sheaf ui state likely can just contain the marks ui state

extra info: pagination info needs to be in the ui state

Sheaf Lattice Definition
alias Vyasa.Sangh.SheafLattice
sangh_session_id = "164eb05d-221a-4939-b180-8394e1a5515f"

R.recompile()
alias Vyasa.Sangh
alias Vyasa.Sangh.Sheaf
alias Vyasa.Written.{Source}
alias Vyasa.Written

# this is the function exposed for it: 
sheaf_lattice = SheafLattice.create_complete_sheaf_lattice(sangh_session_id)


# here's the manual way of doing it
# %{data: root_sheafs} = Sangh.get_root_sheafs_by_session(sangh_session_id, 1)
# levels = [0, 1, 2]
# sheaf_lattice =
#   levels
#   |> Enum.flat_map(fn level ->
#     root_sheafs
#     |> Enum.map(fn sheaf -> to_string(sheaf.path) end)
#     |> Enum.flat_map(fn sheaf_id ->
#       Sangh.get_child_sheafs_by_session(sangh_session_id, sheaf_id, level)
#     end)
#     |> Enum.map(fn s -> {s.path.labels, s} end)
#   end)
#   |> Enum.into(%{})



Reading a sheaf lattice

Here, Iโ€™ve put in examples of using the helper functions for reading sheafs. Test this out by setting test_input correctly to the label of a valid leaf-sheaf, this may change based on the seed function.

alias Vyasa.Sangh.SheafLattice
example_leaf_sheaf = hd(SheafLattice.read_sheaf_lattice(sheaf_lattice, 2, nil))
test_input = [a, b, c] = example_leaf_sheaf.path.labels 
# OR manually set test input in this block:
# test_input = ["8955df0c", "2628a71a", "f9ce1eee"]
# fetch all sheafs in a particular level
SheafLattice.read_sheaf_lattice(sheaf_lattice)
# should return an empty array since it's beyond the max-3 depth
SheafLattice.read_sheaf_lattice(sheaf_lattice, 3, nil)
SheafLattice.read_sheaf_lattice(sheaf_lattice, 0, nil)
SheafLattice.read_sheaf_lattice(sheaf_lattice, 1, nil)
SheafLattice.read_sheaf_lattice(sheaf_lattice, 2, nil)
# ["8955df0c", "2628a71a", "f9ce1eee"],
# # # # fetch based on specific matches of a single label
SheafLattice.read_sheaf_lattice(sheaf_lattice, 0, a)
SheafLattice.read_sheaf_lattice(sheaf_lattice, 1, b)
SheafLattice.read_sheaf_lattice(sheaf_lattice, 2, c)
# # # # # fetch based on complete matches: 
SheafLattice.read_sheaf_lattice(sheaf_lattice, 1, [a, b])
SheafLattice.read_sheaf_lattice(sheaf_lattice, 2, [a, b, c])
# # # # # fetch immediate children based on particular parent
# # # # # fetch immediate children of a specific level 0 node:
SheafLattice.read_sheaf_lattice(sheaf_lattice, 1, [a, nil])
# # # # # fetch immediate children of a specific level 1 node:
SheafLattice.read_sheaf_lattice(sheaf_lattice, 2, [a, b, nil])
SheafLattice.read_sheaf_lattice(sheaf_lattice, 1, ["1bebc4d1", nil])
|> Enum.map(fn s -> s.path.labels end)

# |> Enum.count()

Sheaf UI States

Intent here is to keep ui state auxilliary to the actual data state. The shape of the data and ui lattices shall be exactly the same, so that the same read_sheaf_lattice() can be used on both ui and data state lattices.

R.recompile()

# getting info about the sheaf's marks and stuff: 
[ test_root_sheaf | _]= SheafLattice.read_sheaf_lattice(sheaf_lattice, 0, nil)
  |> Enum.map(fn s -> s |> Vyasa.Repo.preload(:marks) end)
|> Enum.map(fn  
  %{marks: [_|_] = _m} = s -> s
  %{marks: [] = _m} -> nil
    end)
|> Enum.reject(fn s -> is_nil(s) end)

alias VyasaWeb.Context.Components.UiState.Sheaf, as: SheafUiState
SheafUiState.get_initial_ui_state(test_root_sheaf)

# sheaf_lattice 
# |> Enum.map(fn {k, } end)

sheaf_ui_lattice =
      sheaf_lattice
      |> Enum.map(fn {k,
                      %Sheaf{
                        marks: _marks
                      } = sheaf} ->
        sheaf_ui = sheaf |> SheafUiState.get_initial_ui_state()
        {k, sheaf_ui}
      end)
      |> Enum.into(%{})
   

sheaf_lattice |> SheafLattice.read_sheaf_lattice(2, "08e53d81")
sheaf_ui_lattice |> SheafLattice.read_sheaf_lattice(2, "08e53d81")


test_root_sheaf |> SheafUiState.get_initial_ui_state()
# SheafUi

Creation Related Test Section

{:ok, %{id: s_id}} = Vyasa.Sangh.create_session()
{:ok, sangh} = Vyasa.Sangh.create_sheaf(%{id: Ecto.UUID.generate(), session_id: s_id})
{:ok, child_sangh} = Vyasa.Sangh.create_sheaf(%{id: Ecto.UUID.generate(), parent: sangh, session_id: s_id})

{:ok, grandchild_sangh} = Vyasa.Sangh.create_sheaf(%{id: Ecto.UUID.generate(), parent: child_sangh, session_id: s_id})


# sheaf_lattice = Vyasa.Sangh.get_child_sheafs_by_session("a33b4f92-90de-45a0-88b0-c19518491182", "085994cc", 2)
# |> Enum.map(fn sheaf -> 
#   {sheaf.path.labels, sheaf}
#   end)
# |> Enum.into(%{})
# # length(sangh.path.labels)


sheaf_lattice |> Enum.map(fn {["085994cc" | [ "01a1bb12" | _]] = k , sheaf} ->  sheaf 
_ -> nil
  end)
# |> Enum.into(%{})