Collections for Phoenix: records of one kind, with fields your users define, seen as a table, a board, a calendar, a chart or a canvas.
Your app has a module of Cardbox's:
defmodule MyApp.Cardbox do
use Cardbox, repo: MyApp.Repo, pubsub: MyApp.PubSub
endand makes collections, adds fields, and reads and writes records with it:
{:ok, books} =
MyApp.Cardbox.create_collection(%{
name: "Reading list",
owner: {"workspace", 42},
fields: [
%{name: "Title", type: "text"},
%{name: "Status", type: "status"},
%{name: "Rating", type: "number", options: %{format: "integer"}},
%{name: "Finished", type: "date"}
]
})
{:ok, dune} = MyApp.Cardbox.insert(books, %{"Title" => "Dune", "Status" => "In progress"})
{:ok, reading} =
MyApp.Cardbox.list(books, %{
"filter" => %{"field" => "status", "op" => "in_group", "value" => "doing"},
"sort" => [%{"field" => "title"}]
})Options of use Cardbox
:repo- the Ecto repo of the tables (mix cardbox.gen.migration). Without it, the module definesrepo/0, for a repo known only at runtime (a library on Cardbox that takes its host's repo from its config).:pubsub- aPhoenix.PubSub, to broadcast each change to the collection's topic (subscribe/1,Cardbox.Event).:views- types of views of your own, beside Cardbox's (Cardbox.View):views: ["timeline", "map"]. A view of one is saved, listed and queried as any, and yourcheck_view/3checks its options; your app draws it. See the Views guide.
Options of the functions
Every function that writes takes :actor, who writes: the person or the
agent of your app, whose id (actor_id/1) each record keeps as who
made it and changed it last, each event carries, and allow?/3 is
asked about.
Every function that reads takes :me (who "me" is in a filter; the
actor's id, or any id), :time_zone (where the days of date filters
are; UTC unless given) and :now.
Callbacks
Three callbacks have defaults, and your module overrides them:
@impl Cardbox
def actor_id(%MyApp.Accounts.User{id: id}), do: "user:#{id}"
@impl Cardbox
def allow?(%MyApp.Accounts.User{} = user, :change_fields, collection),
do: MyApp.Permissions.allowed?(user, :manage, collection)
def allow?(_actor, _action, _subject), do: true
@impl Cardbox
def check_view("timeline", %{"start" => _, "end" => _} = options, _collection), do: {:ok, options}
def check_view("timeline", _options, _collection), do: {:error, "a timeline names its start and end fields"}
def check_view(_type, options, _collection), do: {:ok, options}
Summary
Callbacks
The id that records and events keep of an actor: by default, its :id as
text, or the actor itself when it is text, or nil.
Whether an actor may do an action: :create_collection (the subject is
the collection's attributes), :update_collection, :delete_collection,
:change_fields, :insert, :update, :delete, :move, :save_view
and :delete_view (the subject is the collection). True by default.
Reads are not asked: scope the collections a person reads, as your app
scopes its own records.
Checks the options of a view of a type of your own (:views), when it is
saved: the options as they are kept, or an error that says what to write.
The options come with their keys as text. Takes them as they are by
default.
Functions
A collection as an agent reads it: its name, and each field with its key, type, choices and the operators it filters by. Give it to a model as the description of a tool that reads or writes the collection.
The value of a field of a record, read for Elixir. See Cardbox.Record.get/3.
The JSON Schema of the values a collection's
records take, by field key: what insert/3 and update/4 take, in the
forms they keep. For a model's structured output, an agent's tool, or a
form of your app that checks values before they are sent.
Callbacks
The id that records and events keep of an actor: by default, its :id as
text, or the actor itself when it is text, or nil.
Whether an actor may do an action: :create_collection (the subject is
the collection's attributes), :update_collection, :delete_collection,
:change_fields, :insert, :update, :delete, :move, :save_view
and :delete_view (the subject is the collection). True by default.
Reads are not asked: scope the collections a person reads, as your app
scopes its own records.
@callback check_view( type :: String.t(), options :: map(), collection :: Cardbox.Collection.t() ) :: {:ok, map()} | {:error, String.t()}
Checks the options of a view of a type of your own (:views), when it is
saved: the options as they are kept, or an error that says what to write.
The options come with their keys as text. Takes them as they are by
default.
Functions
@spec describe(Cardbox.Collection.t()) :: map()
A collection as an agent reads it: its name, and each field with its key, type, choices and the operators it filters by. Give it to a model as the description of a tool that reads or writes the collection.
@spec get(Cardbox.Record.t(), Cardbox.Collection.t(), term()) :: term()
The value of a field of a record, read for Elixir. See Cardbox.Record.get/3.
@spec json_schema(Cardbox.Collection.t()) :: map()
The JSON Schema of the values a collection's
records take, by field key: what insert/3 and update/4 take, in the
forms they keep. For a model's structured output, an agent's tool, or a
form of your app that checks values before they are sent.
Cardbox.json_schema(tasks)
#=> %{
# "$schema" => "https://json-schema.org/draft/2020-12/schema",
# "title" => "Tasks",
# "type" => "object",
# "properties" => %{
# "title" => %{"title" => "Title", "anyOf" => [%{"type" => "string", ...}, %{"type" => "null"}]},
# "status" => %{
# "title" => "Status",
# "anyOf" => [
# %{"type" => "string", "oneOf" => [%{"const" => "not_started", "title" => "Not started"}, ...]},
# %{"type" => "null"}
# ]
# },
# ...
# },
# "additionalProperties" => false
# }Each value may be null, which empties the field. A choice is its id,
with its name as its title; a day is "2026-10-25" (format: date), a
moment ISO 8601 (format: date-time); a relation a list of record ids.
The four read-only types are not in it. Cardbox takes more than the
schema says (a choice's name, a number as text), and checks every value
as it writes it, whatever a schema let through.