Mentor.Ecto.Schema behaviour (mentor v0.2.2)
View SourceProvides functionality to integrate Ecto schemas with the Mentor framework, ensuring that schemas include comprehensive field documentation.
This module defines a behaviour that requires implementing a changeset/2
function and utilizes compile-time hooks to verify that all fields in the schema are documented in the module's @moduledoc
.
Usage
To use Mentor.Ecto.Schema
in your Ecto schema module:
defmodule MyApp.Schema do
use Ecto.Schema
use Mentor.Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :name, :string
field :age, :integer
end
@impl true
def changeset(%__MODULE__{} = source, %{} = attrs) do
source
|> cast(attrs, [:name, :age])
|> validate_required([:name, :age])
|> validate_number(:age, less_than: 100, greater_than: 0)
end
end
Ensure that your module's @moduledoc
includes a "Fields" section documenting each field:
@moduledoc """
Schema representing a person.
## Fields
- `name`: The name of the person.
- `age`: The age of the person.
"""
Custom LLM description
If you don't wanna or can't rely on @moduledoc
to descrive the LLM prompt for your schema, you can alternatively provide a llm_description/0
callback into you schema module that returns a string that represents the prompt it self, like:
@impl true
def llm_description do
"""
## Fields
- `name`: it should be a valid string name for humans
- `age`: it should be a reasonable age number for a human being
"""
end
Ignored fields
Sometimes you wanna use an Ecto schema field only for internal logic or even have different changesets functions that can cast on different set of fields and for so you would like to avoid to send these fields to the LLM and avoid the strictness of filling the description for these fields in the @moduledoc
.
In this case you can pass an additional option while using this module, called ignored_fields
, passing a list of atoms with the fields names to be ignored, for instance:
defmodule MyApp.Schema do
use Ecto.Schema
use Mentor.Ecto.Schema, ignored_fields: [:timestamps]
import Ecto.Changeset
@timestamps_opts [inserted_at: :created_at]
@primary_key false
embedded_schema do
field :name, :string
field :age, :integer
timestamps()
end
@impl true
def changeset(%__MODULE__{} = source, %{} = attrs) do
source
|> cast(attrs, [:name, :age])
|> validate_required([:name, :age])
|> validate_number(:age, less_than: 100, greater_than: 0)
end
end
One so common use case for this option, as can be seen on the aboce example are the timestamps fields that Ecto generate, so for this special case you can inform :timestamps
as an ignored field to ignore both [:inserted_at, :updated_at]
, even if you define custom aliases for it with the @timestamps_opts
attribute, like :created_at
.
You can also pass partial timestamps fields to be ignored, like only ignore :created_at
or :updated_at
.
Warning
Defining timestamps aliases with the macro timestamps/1
inside the schema itself, aren't supported, since i didn't discover how to get this data from on compile time to filter as ignored fields, sou you can either define these options as the attribute as said above, or pass the individual aliases names into the ignored_fields
options.
Summary
Functions
Validates the given data against the specified schema by applying the schema's changeset/2
function.
Callbacks
@callback changeset(Ecto.Schema.t(), map()) :: Ecto.Changeset.t()
@callback llm_description() :: String.t()
Functions
Validates the given data against the specified schema by applying the schema's changeset/2
function.
Parameters
schema
: The schema module implementing theMentor.Ecto.Schema
behaviour.data
: A map containing the data to be validated.
Returns
{:ok, struct}
: If the data is valid and conforms to the schema.{:error, changeset}
: If the data is invalid, returns the changeset with errors.
Examples
iex> data = %{"name" => "Alice", "age" => 30}
iex> Mentor.Ecto.Schema.validate(MyApp.Schema, data)
{:ok, %MyApp.Schema{name: "Alice", age: 30}}
iex> invalid_data = %{"name" => "Alice", "age" => 150}
iex> Mentor.Ecto.Schema.validate(MyApp.Schema, invalid_data)
{:error, %Ecto.Changeset{errors: [age: {"must be less than 100", [validation: :number, less_than: 100]}]}}