Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fields related documentation #417

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/guides/compose-a-domain/activate-domain.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Activate the domain

Once a domain object is defined, the next step is to activate it.

A `Domain` in protean is always associated with a domain context, which can be
used to bind an domain object implicitly to the current thread or greenlet. We
refer to the act of binding the domain object as **activating the domain**.
Expand Down
174 changes: 0 additions & 174 deletions docs/guides/domain-definition/fields.md

This file was deleted.

101 changes: 101 additions & 0 deletions docs/guides/domain-definition/fields/association-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Association Fields

## `HasOne`

Represents an one-to-one association between an aggregate and its entities.
This field is used to define a relationship where an aggregate is associated
with at most one instance of a child entity.

```python hl_lines="10 13"
{! docs_src/guides/domain-definition/fields/association-fields/001.py !}
```

!!!note
If you carefully observe the `HasOne` field declaration, the child entity's
name is a string value! This is usually the way to avoid circular references.
It applies to all aspects of Protean that link two entities - the string
value will be resolved to the class at runtime.

The `Author` entity can now be persisted along with the `Book` aggregate:

```shell hl_lines="3 12-13"
In [1]: book = Book(
...: title="The Great Gatsby",
...: author=Author(name="F. Scott Fitzgerald")
...: )

In [2]: domain.repository_for(Book).add(book)
Out[2]: <Book: Book object (id: a4a642d9-87ed-44de-9889-c687466f171b)>

In [3]: domain.repository_for(Book)._dao.query.all().items[0].to_dict()
Out[3]:
{'title': 'The Great Gatsby',
'author': {'name': 'F. Scott Fitzgerald',
'id': '1f275e92-9872-4d96-b999-4ef0fbe61013'},
'id': 'a4a642d9-87ed-44de-9889-c687466f171b'}
```

!!!note
Protean adds a `Reference` field to child entities to preserve the inverse
relationship - from child entity to aggregate - when persisted. This is
visible if you introspect the fields of the Child Entity.

```shell hl_lines="7 13"
In [1]: from protean.reflection import declared_fields, attributes

In [2]: declared_fields(Author)
Out[2]:
{'name': String(required=True, max_length=50),
'id': Auto(identifier=True),
'book': Reference()}

In [3]: attributes(Author)
Out[3]:
{'name': String(required=True, max_length=50),
'id': Auto(identifier=True),
'book_id': _ReferenceField()}
```

We will further review persistence related aspects around associations in the
Repository section.
<!-- FIXME Link Repository section -->

## `HasMany`

Represents a one-to-many association between two entities. This field is used
to define a relationship where an aggregate has multiple instances of a child
entity.

```python hl_lines="11"
{! docs_src/guides/domain-definition/fields/association-fields/002.py !}
```

Protean provides helper methods that begin with `add_` and `remove_` to add
and remove child entities from the `HasMany` relationship.

```shell hl_lines="4-5 12-13 16 23"
In [1]: post = Post(
...: title="Foo",
...: comments=[
...: Comment(content="Bar"),
...: Comment(content="Baz")
...: ]
...: )

In [2]: post.to_dict()
Out[2]:
{'title': 'Foo',
'comments': [{'content': 'Bar', 'id': '085ed011-15b3-48e3-9363-99a53bc9362a'},
{'content': 'Baz', 'id': '4790cf87-c234-42b6-bb03-1e0599bd6c0f'}],
'id': '29943ac9-a9eb-497b-b6d2-466b30ecd5f5'}

In [3]: post.add_comments(Comment(content="Qux"))

In [4]: post.to_dict()
Out[4]:
{'title': 'Foo',
'comments': [{'content': 'Bar', 'id': '085ed011-15b3-48e3-9363-99a53bc9362a'},
{'content': 'Baz', 'id': '4790cf87-c234-42b6-bb03-1e0599bd6c0f'},
{'content': 'Qux', 'id': 'b1a7aeda-81ca-4d0b-9d7e-6fe0c000b8af'}],
'id': '29943ac9-a9eb-497b-b6d2-466b30ecd5f5'}
```
105 changes: 105 additions & 0 deletions docs/guides/domain-definition/fields/container-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Container Fields

## `List`

A field that represents a list of values.

**Optional Arguments**

- **`content_type`**: The type of items in the list. Defaults to `String`.
Accepted field types are `Boolean`, `Date`, `DateTime`, `Float`, `Identifier`,
`Integer`, `String`, and `Text`.
- **`pickled`**: Whether the list should be pickled when stored. Defaults to
`False`.

!!!note
Some database implementations (like Postgresql) can store lists by default.
You can force it to store the pickled value as a Python object by
specifying `pickled=True`. Databases that don’t support lists simply store
the field as a python object.

```python hl_lines="9"
{! docs_src/guides/domain-definition/fields/simple-fields/001.py !}
```

The value is provided as a `list`, and the values in the `list` are validated
to be of the right type.

```shell hl_lines="6 12"
In [1]: user = User(email="[email protected]", roles=['ADMIN', 'EDITOR'])

In [2]: user.to_dict()
Out[2]:
{'email': '[email protected]',
'roles': ['ADMIN', 'EDITOR'],
'id': '582d946b-409b-4b15-b3be-6a90284264b3'}

In [3]: user2 = User(email="[email protected]", roles=[1, 2])
ERROR: Error during initialization: {'roles': ['Invalid value [1, 2]']}
...
ValidationError: {'roles': ['Invalid value [1, 2]']}
```

## `Dict`

A field that represents a dictionary.

**Optional Arguments**

- **`pickled`**: Whether the dict should be pickled when stored. Defaults to
`False`.

```python hl_lines="10"
{! docs_src/guides/domain-definition/fields/container-fields/002.py !}
```

A regular dictionary can be supplied as value to payload:


```shell hl_lines="3 9"
In [1]: event=UserEvent(
...: name="UserRegistered",
...: payload={'name': 'John Doe', 'email': '[email protected]'}
...: )

In [2]: event.to_dict()
Out[2]:
{'name': 'UserRegistered',
'payload': {'name': 'John Doe', 'email': '[email protected]'},
'id': '44e9143f-f4a6-40da-9128-4b6c013420d4'}
```

!!!note
Some database implementations (like Postgresql) can store dicts as JSON
by default. You can force it to store the pickled value as a Python object
by specifying pickled=True. Databases that don’t support lists simply store
the field as a python object.

## `ValueObject`

Represents a field that holds a value object. This field is used to embed a
Value Object within an entity.

**Arguments**

- **`value_object_cls`**: The class of the value object to be embedded.

```python hl_lines="7-15 20"
{! docs_src/guides/domain-definition/fields/container-fields/003.py !}
```

You can provide an instance of the Value Object as input to the value object
field:

```shell hl_lines="2 8"
In [1]: account = Account(
...: balance=Balance(currency="USD", amount=100.0),
...: name="Checking"
...: )

In [2]: account.to_dict()
Out[2]:
{'balance': {'currency': 'USD', 'amount': 100.0},
'name': 'Checking',
'id': '513b8a78-e00f-45ce-bb6f-11ef0cccbec6'}
```
Loading
Loading