Liquex.Drop behaviour (liquex v0.15.0)

Copy Markdown View Source

Behaviour for context-aware indifferent-access "drops".

A drop is a struct whose module declares @behaviour Liquex.Drop (or use Liquex.Drop) and exposes attributes via fetch/3. Liquex's resolver dispatches . traversals ({{ products.first.category.name }}) through fetch/3, passing the current Liquex.Context so the drop can:

  • Read context.cache_prefix, context.private, or other variables while resolving its own fields.
  • Have its results automatically memoized for the duration of a single render — repeat references to the same {drop, key} pair within one Liquex.render!/2 call only invoke fetch/3 once.

Plain structs without @behaviour Liquex.Drop are still readable from templates via direct field access (atom or string keys), but they cannot resolve dynamic keys, run computations on access, or participate in the per-render cache.

Two ways to define a drop

Authors declare exposed attributes as regular functions; fetch/3 is generated for them, restricted to the declared whitelist. Functions are also directly callable from Elixir (MyDrop.index(drop, ctx)).

defmodule MyApp.ForloopDrop do
  use Liquex.Drop, cacheable: false

  defstruct [:index, :length]

  defliquid index(drop, _ctx),  do: drop.index + 1
  defliquid index0(drop, _ctx), do: drop.index
  defliquid first(drop, _ctx),  do: drop.index == 0
  defliquid last(drop, _ctx),   do: drop.index == drop.length - 1
end

Pass cacheable: false for drops whose attributes are cheap pure computations on struct fields — caching just bloats the per-render cache with values that are faster to recompute than to look up.

Manual @behaviour (escape hatch)

When you need full control over dispatch (custom error semantics, dynamic attribute lists, atom-key fallbacks, etc.), implement the behaviour by hand:

defmodule MyApp.ProductsDrop do
  @behaviour Liquex.Drop
  defstruct [:scope]

  @impl true
  def fetch(%__MODULE__{scope: scope}, key, _context)
      when key in ["first", :first] do
    {:ok, MyApp.Repo.one(from p in scope, limit: 1)}
  end

  def fetch(_, _, _), do: :error
end

Opting out of memoization

Stateful drops (paginators, generators) or drops whose attributes are cheaper to recompute than to cache should opt out:

def cacheable?(_drop), do: false

Or via use Liquex.Drop, cacheable: false. Default is true when cacheable?/1 is not defined.

Summary

Callbacks

Whether results from fetch/3 should be memoized for the rest of the current render. Defaults to true when not implemented.

Resolve key against drop for the given render context.

Functions

Whether drop's module wants its fetch/3 results cached. Defaults to true when the optional cacheable?/1 callback isn't defined.

Define a drop attribute.

Whether the given struct's module is registered as a Liquex.Drop.

Types

key()

@type key() :: any()

Callbacks

cacheable?(drop)

(optional)
@callback cacheable?(drop :: struct()) :: boolean()

Whether results from fetch/3 should be memoized for the rest of the current render. Defaults to true when not implemented.

fetch(drop, key, t)

@callback fetch(drop :: struct(), key(), Liquex.Context.t()) :: {:ok, any()} | :error

Resolve key against drop for the given render context.

Returns {:ok, value} on success or :error to signal "no such key" — the resolver will then try string/atom alternates and finally produce nil.

Functions

cacheable?(drop)

@spec cacheable?(struct()) :: boolean()

Whether drop's module wants its fetch/3 results cached. Defaults to true when the optional cacheable?/1 callback isn't defined.

defliquid(call, list)

(macro)

Define a drop attribute.

Accepts a normal function head — defliquid name(drop, ctx), do: … — and registers name as an exposed attribute. The framework's auto-generated fetch/3 will dispatch the string key "name" to this function. The function is also a regular public function on the module.

drop?(value)

@spec drop?(any()) :: boolean()

Whether the given struct's module is registered as a Liquex.Drop.