Traits
A trait packages members, criteria, and rules so that many entities can share them. Traits are how Elfie composes behavior, and how targets, frameworks, and team conventions reach your code.
Declaring and applying
trait timestamped: `Records when it was made and last changed` {
$createdAt: `When it was first saved` = string;
$updatedAt: `When it was last saved` = string;
where (`It is saved for the first time`) -> `[[timestamped.createdAt]] and [[timestamped.updatedAt]] are the same moment`;
}
d Note is timestamped: `A short note a user writes` {
$text: `What the note says` = string;
}Applying a trait with is runs its body against the entity:
- members declared with
$join the entity's scope, soNotenow hascreatedAtandupdatedAt - value setters such as
.provides = []become the entity's values - criteria and
whererules join the entity's criteria, with the trait recorded as their contributor
Apply several traits with commas: d Note is audited, owned(User).
Parameters
/// owner: the data that owns it
trait owned(owner: Data): `Belongs to exactly one [[&owner]]` {
$ownerId: `The id of its owner` = string;
where (`Its owner is deleted`) -> `It is deleted with it`;
}Arguments are evaluated when the trait is applied. Use parameters in members, setters, where rules, or criteria. A parameter that appears only in a description is a defect, because nothing checks it. [[&owner]] refers to the entity passed in, so owned(User) reads Belongs to exactly one User.
Passing too many or too few arguments is reported at the application.
Inheritance
trait persisted: `Kept in the store and read back unchanged` {
where (`An entity carrying it is written`) -> `Reading it back gives an equal value`;
}
trait audited extends persisted, timestamped: `Every change to it is recorded` {
where (`It changes`) -> `One audit record names {{@identifier}} and what changed`;
}Applying audited applies persisted and timestamped first, up the whole chain. A trait reached more than once for one entity, through several paths with equal arguments, is applied once.
Rules for other entities
A with block inside a trait attaches its criteria to another entity, once for each entity the trait is applied to:
trait slugged: `Reachable at a URL friendly slug` {
with Page {
where (`A [[Page]] is built for {{@identifier}}`) -> `Its path ends in a slug of the title`;
}
}Applying from outside
apply puts a trait on an entity, a list of entities, the current file, or the whole program.
timestamped.apply(Draft);
timestamped.apply([Draft, Note]);
persisted.apply(@);
logged.apply(global);At the top level of a file, @ is the file itself, so persisted.apply(@) applies the trait to every entity the file declares. global stands for every entity of every file. See Global properties.
Looking traits up
| Expression | Gives |
|---|---|
persisted@entities | Everything the trait was applied to, directly or by inheritance. |
persisted@extenders | Every trait that extends it. |
Note@traits | Every trait applied to Note, in application order. |
Traits as types
(is trait) is the type of any value whose type carries the trait.
/// content: what the card shows
fn Card(content: (is persisted)): `Shows one stored thing` => string {
@acceptanceCriteria.add({ behavior = `Shows every member of [[content]]` });
}This site uses the same pattern: fn Button(content: (is component)) takes any React component.
Traits from packages
Packages connect your code to a target through traits. A framework in your target says how each one is built.
| Trait | Package | Makes an fn into |
|---|---|---|
page(path) | http | A page at a URL. |
endpoint(method, path) | http | One operation of a JSON API. |
component | react | A React component. |
command(name) | cli | A subcommand, or the program itself when name is empty. |
fn Documentation() is page('/docs'), documentationPage {}Naming
| Do | Don't |
|---|---|
Name traits so X is trait reads as a property: persisted, owned(User), triedBefore(other). | Name traits as verbs or commands: persists, binds, uses. |
| Make marker traits parameterless. | Add a parameter that every application passes the same value to. |
| Apply a trait once per entity. | Apply the same trait twice to one entity. |
Was this guide clear?
Share a suggestion