sqlmodel/docs/tutorial/many-to-many/update-remove-relationships.md
Sebastián Ramírez 6d1d86ab85 📝 Add docs
2021-08-24 15:02:48 +02:00

252 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Update and Remove Many-to-Many Relationships
Now we'll see how to update and remove these **many-to-many** relationships.
We'll continue from where we left off with the previous code.
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/many_to_many/tutorial001.py!}
```
</details>
## Get Data to Update
Let's now create a function `update_heroes()`.
We'll get **Spider-Boy** and the **Z-Force** team.
As you already know how these goes, I'll use the **short version** and get the data in a single Python statement.
And because we are now using `select()`, we also have to import it.
```Python hl_lines="3 7-12"
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:1-3]!}
# Some code here omitted 👈
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-83]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
```
</details>
And of course, we have to add `update_heroes()` to our `main()` function:
```Python hl_lines="6"
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:100-107]!}
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
```
</details>
## Add Many-to-Many Relationships
Now let's imagine that **Spider-Boy** thinks that the **Z-Force** team is super cool and decides to go there and join them.
We can use the same **relationship attributes** to include `hero_spider_boy` in the `team_z_force.heroes`.
```Python hl_lines="10-12 14-15"
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-90]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
```
</details>
!!! tip
Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically.
So we don't have to call `session.refresh()`.
We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm.
Notice that we only `add` **Z-Force** to the session, then we commit.
We never add **Spider-Boy** to the session, and we never even refresh it. But we still print his teams.
This still works correctly because we are using `back_populates` in the `Relationship()` in the models. That way, **SQLModel** (actually SQLAlchemy) can keep track of the changes and updates, and make sure they also happen on the relationships in the other related models. 🎉
## Run the Program
You can confirm it's all working by running the program in the command line:
<div class="termy">
```console
$ python app.py
// Previous output omitted 🙈
// Create the new many-to-many relationship
INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?)
INFO Engine [generated in 0.00020s] (1, 3)
INFO Engine COMMIT
// Start a new automatic transaction
INFO Engine BEGIN (implicit)
// Automatically refresh the data while accessing the attribute .teams
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00044s] (3,)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.1648s ago] (3,)
// Print Spider-Boy teams, including Z-Force 🎉
Updated Spider-Boy's Teams: [
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
Team(id=1, name='Z-Force', headquarters='Sister Margarets Bar')
]
// Automatically refresh the data while accessing the attribute .heores
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
INFO Engine [cached since 0.1499s ago] (1,)
// Print Z-Force heroes, including Spider-Boy 🎉
Z-Force heroes: [
Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson'),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', teams=[
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
Team(id=1, name='Z-Force', headquarters='Sister Margarets Bar', heroes=[...])
])
]
```
</div>
## Remove Many-to-Many Relationships
Now let's say that right after joining the team, **Spider-Boy** realized that their "life preserving policies" are much more relaxed than what he's used to. 💀
And their *occupational safety and health* is also not as great... 💥
So, **Spider-Boy** decides to leave **Z-Force**.
Let's update the relationships to remove `team_z_force` from `hero_spider_boy.teams`.
Because `hero_spider_boy.teams` is just a list (a special list managed by SQLAlchemy, but a list), we can use the standard list methods.
In this case, we use the method `.remove()`, that takes an item and removes it from the list.
```Python hl_lines="17-19 21-22"
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-97]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/many_to_many/tutorial002.py!}
```
</details>
And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even add `hero_spider_boy`.
This still works because by updating the teams in `hero_spider_boy`, because they are synchronized with `back_populates`, the changes are also reflected in `team_z_force`, so it also has changes to be saved in the DB (that **Spider-Boy** was removed).
And then we add the team, and commit the changes, which updates the `team_z_force` object, and because it changed the table that also had a connection with the `hero_spider_boy`, it is also marked internally as updated, so it all works.
And then we just print them again to confirm that everything worked correctly.
## Run the Program Again
To confirm that this last part worked, you can run the program again, it will output something like:
<div style="font-size: 1rem;" class="termy">
```console
$ python app.py
// Previous output omitted 🙈
// Delete the row in the link table
INFO Engine DELETE FROM heroteamlink WHERE heroteamlink.team_id = ? AND heroteamlink.hero_id = ?
INFO Engine [generated in 0.00043s] (1, 3)
// Save the changes
INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Automatically refresh the data while accessing the attribute .heroes
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00029s] (1,)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
INFO Engine [cached since 0.5625s ago] (1,)
// Print the Z-Force heroes after reverting the changes
Reverted Z-Force's heroes: [
Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson')
]
// Automatically refresh the data while accessing the attribute .teams
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.4209s ago] (3,)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.5842s ago] (3,)
// Print Spider-Boy's teams after reverting the changes
Reverted Spider-Boy's teams: [
Team(id=2, name='Preventers', headquarters='Sharp Tower')
]
// Automatically roll back any possible previously unsaved transaction
INFO Engine ROLLBACK
```
</div>
## Recap
Updating and removing many-to-many relationships is quite straightforward after setting up the **link model** and the relationship attributes.
You can just use common list operation. 🚀