mirror of
https://github.com/ent/ent.git
synced 2026-05-22 09:31:45 +03:00
ent/doc: transaction example and docs
Summary: {F205899335}
Reviewed By: dlvhdr
Differential Revision: D17149531
fbshipit-source-id: cb8595d41ede6f813370564ca688f33d0dfe6905
This commit is contained in:
committed by
Facebook Github Bot
parent
4323141fe2
commit
9b7ea021ef
@@ -5,10 +5,10 @@ sidebar_label: Quick Introduction
|
||||
---
|
||||
|
||||
`ent` is a simple, yet powerful entity framework for Go built with the following principles:
|
||||
- Model your data as graph easily.
|
||||
- Easily modeling your data as a graph structure.
|
||||
- Defining your schema as code.
|
||||
- Static typing first based on code generation.
|
||||
- Make the work with graph-like data in Go easier.
|
||||
- Static typing based on code generation.
|
||||
- Simplifying graph traversals.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -51,7 +51,7 @@ func (User) Edges() []ent.Edge {
|
||||
|
||||
```
|
||||
|
||||
Let's add 2 fields to the `User` schema, and then run `entc generate`:
|
||||
Add 2 fields to the `User` schema, and then run `entc generate`:
|
||||
|
||||
```go
|
||||
package schema
|
||||
@@ -73,13 +73,13 @@ func (User) Fields() []ent.Field {
|
||||
}
|
||||
```
|
||||
|
||||
Running `entc generate` from the root directory of the project:
|
||||
Run `entc generate` from the root directory of the project:
|
||||
|
||||
```go
|
||||
$ entc generate ./ent/schema
|
||||
```
|
||||
|
||||
Will produce the following files:
|
||||
This produces the following files:
|
||||
```
|
||||
ent
|
||||
├── client.go
|
||||
@@ -108,8 +108,7 @@ ent
|
||||
|
||||
## Create Your First Entity
|
||||
|
||||
First thing we need to do, is creating a new `ent.Client`. For the example purpose,
|
||||
we will use SQLite3.
|
||||
To get started, create a new `ent.Client`. For this example, we will use SQLite3.
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -141,7 +140,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
Now, we're ready to create our user. Let's call this function `Do` for the sake of the example:
|
||||
Now, we're ready to create our user. Let's call this function `Do` for the sake of example:
|
||||
```go
|
||||
func Do(ctx context.Context, client *ent.Client) (*ent.User, error) {
|
||||
u, err := client.User.
|
||||
@@ -160,7 +159,7 @@ func Do(ctx context.Context, client *ent.Client) (*ent.User, error) {
|
||||
## Query Your Entities
|
||||
|
||||
`entc` generates a package for each entity schema that contains its predicates, default values, validators
|
||||
and information about storage elements (like, column names, primary keys, etc).
|
||||
and additional information about storage elements (column names, primary keys, etc).
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -189,15 +188,15 @@ func Query(ctx context.Context, client *ent.Client) (*ent.User, error) {
|
||||
|
||||
|
||||
## Add Your First Edge (Relation)
|
||||
In this part of the tutorial, we want to declare an edge to another entity in the schema.
|
||||
In this part of the tutorial, we want to declare an edge (relation) to another entity in the schema.
|
||||
Let's create 2 additional entities named `Car` and `Group` with a few fields. We use `entc`
|
||||
to generate the initial schema:
|
||||
to generate the initial schemas:
|
||||
|
||||
```console
|
||||
$ entc init Car Group
|
||||
```
|
||||
|
||||
And then, we add the rest of the fields manually:
|
||||
And then we add the rest of the fields manually:
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
@@ -226,7 +225,7 @@ func (Group) Fields() []ent.Field {
|
||||
```
|
||||
|
||||
Let's define our first relation. An edge from `User` to `Car` defining that a user
|
||||
can have 1 or more cars, but a car has only one owner (one-to-many relation).
|
||||
can **have 1 or more** cars, but a car **has only one** owner (one-to-many relation).
|
||||
|
||||

|
||||
|
||||
@@ -248,7 +247,7 @@ Let's add the `"cars"` edge to the `User` schema, and run `entc generate ./ent/s
|
||||
}
|
||||
```
|
||||
|
||||
We continue our example, by creating 2 cars, and add them to a user.
|
||||
We continue our example by creating 2 cars and adding them to a user.
|
||||
```go
|
||||
func Do(ctx context.Context, client *ent.Client) error {
|
||||
// creating new car with model "Tesla".
|
||||
@@ -285,7 +284,7 @@ func Do(ctx context.Context, client *ent.Client) error {
|
||||
log.Println("user was created: %v", a8m)
|
||||
}
|
||||
```
|
||||
But, what about querying the "cars" edge? Here's how we do it:
|
||||
But what about querying the `cars` edge (relation)? Here's how we do it:
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
@@ -316,16 +315,16 @@ func Do(ctx context.Context, client *ent.Client) error {
|
||||
```
|
||||
|
||||
## Add Your First Inverse Edge (BackRef)
|
||||
Assume we have a `Car` object and we want to get its owner; The user that this car belongs to.
|
||||
Assume we have a `Car` object and we want to get its owner; the user that this car belongs to.
|
||||
For this, we have another type of edge called "inverse edge" that is defined using the `edge.From`
|
||||
function.
|
||||
|
||||

|
||||
|
||||
The new edge created in the diagram above is transparent, to emphasis that we don't create another
|
||||
edge in the database, and it is just a back-reference to the real edge.
|
||||
The new edge created in the diagram above is translucent, to emphasize that we don't create another
|
||||
edge in the database. It's just a back-reference to the real edge (relation).
|
||||
|
||||
Let's add an inverse edge named `"owner"` to the `Car` schema, reference it to the `"cars"` edge
|
||||
Let's add an inverse edge named `owner` to the `Car` schema, reference it to the `cars` edge
|
||||
in the `User` schema, and run `entc generate ./ent/schema`.
|
||||
|
||||
```go
|
||||
@@ -381,13 +380,13 @@ func Do(ctx context.Context, client *ent.Client) error {
|
||||
|
||||
## Create Your Second Edge
|
||||
|
||||
We'll continue our example, by creating a M2M relationship between users and groups.
|
||||
We'll continue our example by creating a M2M (many-to-many) relationship between users and groups.
|
||||
|
||||

|
||||
|
||||
As you can see, each group entity can have many users, and a user can be connected to many groups.
|
||||
A simple "many-to-many" relationship. In the above illustration, the `Group` schema is the owner
|
||||
of the `users` edge (relationship), and the `User` entity has a back-reference/inverse edge to this
|
||||
As you can see, each group entity can **have many** users, and a user can **be connected to many** groups;
|
||||
a simple "many-to-many" relationship. In the above illustration, the `Group` schema is the owner
|
||||
of the `users` edge (relation), and the `User` entity has a back-reference/inverse edge to this
|
||||
relationship named `groups`. Let's define this relationship in our schemas:
|
||||
|
||||
- `<project>/ent/schema/group.go`:
|
||||
@@ -435,15 +434,15 @@ relationship named `groups`. Let's define this relationship in our schemas:
|
||||
}
|
||||
```
|
||||
|
||||
We run `entc` on the schema directory, to re-generate the assets.
|
||||
We run `entc` on the schema directory to re-generate the assets.
|
||||
```cosole
|
||||
$ entc generate ./ent/schema
|
||||
```
|
||||
|
||||
## Run Your First Graph Traversal
|
||||
|
||||
In order to run our first graph traversal, we need to generate some data (nodes and edges).
|
||||
Let's create the following graph using the framework:
|
||||
In order to run our first graph traversal, we need to generate some data (nodes and edges, or in other words,
|
||||
entities and relations). Let's create the following graph using the framework:
|
||||
|
||||

|
||||
|
||||
@@ -517,9 +516,9 @@ func CreateGraph(ctx context.Context, client *ent.Client) error {
|
||||
}
|
||||
```
|
||||
|
||||
Now, when we have a graph with data, we can run a few queries on it:
|
||||
Now when we have a graph with data, we can run a few queries on it:
|
||||
|
||||
1. Get all user's cars of group named "Github":
|
||||
1. Get all user's cars within the group named "Github":
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -543,7 +542,7 @@ Now, when we have a graph with data, we can run a few queries on it:
|
||||
}
|
||||
```
|
||||
|
||||
2. Changing the query above, so that the source of the traversal is the user *Ariel*:
|
||||
2. Change the query above, so that the source of the traversal is the user *Ariel*:
|
||||
|
||||
```go
|
||||
import (
|
||||
@@ -575,7 +574,7 @@ Now, when we have a graph with data, we can run a few queries on it:
|
||||
}
|
||||
```
|
||||
|
||||
3. Get all groups that have users (query with look-aside):
|
||||
3. Get all groups that have users (query with a look-aside predicate):
|
||||
|
||||
```go
|
||||
import (
|
||||
|
||||
@@ -63,4 +63,5 @@ $ entc init User Group
|
||||
If you are used to the definition of relations over edges, that's fine.
|
||||
The modeling is the same. You can model with `ent` whatever you can model
|
||||
with other traditional ORMs.
|
||||
There are many examples in this website that will help you to get started.
|
||||
There are many examples in this website that will help you to get started,
|
||||
and can be found in the [edges section](schema-edges.md).
|
||||
|
||||
@@ -5,8 +5,129 @@ title: Transactions
|
||||
|
||||
## Starting A Transaction
|
||||
|
||||
```go
|
||||
// GenTx generates group of entities in a transaction.
|
||||
func GenTx(ctx context.Context, client *ent.Client) error {
|
||||
tx, err := client.Tx(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting a transaction: %v", err)
|
||||
}
|
||||
hub, err := tx.Group.
|
||||
Create().
|
||||
SetName("Github").
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return rollback(tx, fmt.Errorf("failed creating the group: %v", err))
|
||||
}
|
||||
// Create the admin of the group.
|
||||
dan, err := tx.User.
|
||||
Create().
|
||||
SetAge(29).
|
||||
SetName("Dan").
|
||||
AddManage(hub).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return rollback(tx, err)
|
||||
}
|
||||
// Create user "Ariel".
|
||||
a8m, err := tx.User.
|
||||
Create().
|
||||
SetAge(30).
|
||||
SetName("Ariel").
|
||||
AddGroups(hub).
|
||||
AddFriends(dan).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return rollback(tx, err)
|
||||
}
|
||||
fmt.Println(a8m)
|
||||
// Output:
|
||||
// User(id=2, age=30, name=Ariel)
|
||||
|
||||
// Commit the transaction.
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// rollback calls to tx.Rollback and wraps the given error
|
||||
// with the rollback error if occurred.
|
||||
func rollback(tx *ent.Tx, err error) error {
|
||||
if rerr := tx.Rollback(); rerr != nil {
|
||||
err = fmt.Errorf("%v: %v", err, rerr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
The full example exists in [GitHub](https://github.com/facebookincubator/ent/tree/master/examples/traversal).
|
||||
|
||||
## Transactional Client
|
||||
|
||||
Sometimes, you have an existing code that already work with `*ent.Client`, and you want to change it (or wrap it)
|
||||
to interact with transactions. For these use cases, you have a transactional client. An `*ent.Client` that you can
|
||||
get from an existing transaction.
|
||||
|
||||
```go
|
||||
// WrapGen wraps the existing "Gen" function in a transaction.
|
||||
func WrapGen(ctx context.Context, client *ent.Client) error {
|
||||
tx, err := client.Tx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txClient := tx.Client()
|
||||
// Use the "Gen" below, but give it the transactional client; no code changes to "Gen".
|
||||
if err := Gen(ctx, txClient); err != nil {
|
||||
return rollback(tx, err)
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Gen generates a group of entities.
|
||||
func Gen(ctx context.Context, client *ent.Client) error {
|
||||
// ...
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
The full example exists in [GitHub](https://github.com/facebookincubator/ent/tree/master/examples/traversal).
|
||||
|
||||
## Best Practices
|
||||
|
||||
Reusable function that runs callbacks in a transaction:
|
||||
|
||||
```go
|
||||
func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
|
||||
tx, err := client.Tx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
tx.Rollback()
|
||||
panic(v)
|
||||
}
|
||||
}()
|
||||
if err := fn(tx); err != nil {
|
||||
if rerr := tx.Rollback(); rerr != nil {
|
||||
err = errors.Wrapf(err, "rolling back transaction: %v", rerr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return errors.Wrapf(err, "committing transaction: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Its usage:
|
||||
|
||||
```go
|
||||
func Do(ctx context.Context, client *ent.Client) {
|
||||
// WithTx helper.
|
||||
if err := WithTx(ctx, client, func(tx *ent.Tx) error {
|
||||
return Gen(ctx, tx.Client())
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -40,7 +40,7 @@ class Footer extends React.Component {
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Credits</h5>
|
||||
<h5>Credit</h5>
|
||||
<span className="copyright">
|
||||
The Go gopher was designed by{' '}
|
||||
<a
|
||||
|
||||
@@ -21,7 +21,7 @@ const Block = props => (
|
||||
<div className="block">
|
||||
<div className="blockTitle">
|
||||
<div className="blockTitleText">{props.title}</div>{' '}
|
||||
<div className="yellowArrow">{arrow}</div>
|
||||
<a className="yellowArrow" href={props.link}>{arrow}</a>
|
||||
</div>
|
||||
<div className="blockContent">{props.content}</div>
|
||||
</div>
|
||||
@@ -32,14 +32,17 @@ const Features = () => (
|
||||
<Block
|
||||
title="Schema As Code"
|
||||
content="Simple API for modeling any graph schema as Go objects"
|
||||
link="docs/schema-def"
|
||||
/>
|
||||
<Block
|
||||
title="Easily Traverse Any Graph"
|
||||
content="Run queries, aggregations and traverse any graph structure easily"
|
||||
link="docs/traversals"
|
||||
/>
|
||||
<Block
|
||||
title="Statically Typed And Explicit API"
|
||||
content="100% statically types and explicit api using code generation"
|
||||
content="100% statically typed and explicit API using code generation"
|
||||
link="docs/code-gen"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -75,7 +78,7 @@ class HomeSplash extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<p className="projectDesc">
|
||||
A simple API for modeling any graph using scema as Go objects
|
||||
A simple API for modeling any graph using schema as Go objects
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,10 +30,8 @@ const siteConfig = {
|
||||
|
||||
// Used for publishing and more
|
||||
projectName: 'ent',
|
||||
organizationName: 'facebook',
|
||||
// For top-level user or org sites, the organization is still the same.
|
||||
// e.g., for the https://JoelMarcey.github.io site, it would be set like...
|
||||
// organizationName: 'JoelMarcey'
|
||||
organizationName: 'facebookincubator',
|
||||
|
||||
|
||||
customDocsPath: 'md',
|
||||
// For no header links in the top nav bar -> headerLinks: [],
|
||||
@@ -41,14 +39,13 @@ const siteConfig = {
|
||||
{doc: 'getting-started', label: 'Docs'},
|
||||
{doc: 'getting-started', label: 'GoDoc'},
|
||||
{href: 'https://github.com/facebookincubator/ent', label: 'Github'},
|
||||
{href: 'https://github.com/facebookincubator/ent/issues', label: 'Help'},
|
||||
],
|
||||
|
||||
// If you have users set above, you add it here:
|
||||
users,
|
||||
|
||||
/* path to images for header/footer */
|
||||
headerIcon: 'img/favicon.ico',
|
||||
headerIcon: 'img/logo.png',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
/* Colors for website */
|
||||
|
||||
@@ -90,6 +90,37 @@
|
||||
@media only screen and (min-width: 1400px) {
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.sideNavVisible .headerWrapper.wrapper header > a {
|
||||
display: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.gopherGraph {
|
||||
margin-top: 25px;
|
||||
}
|
||||
.features {
|
||||
margin: 60px auto 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 400px) {
|
||||
.gopherGraph {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.features {
|
||||
margin: 30px auto 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
.gettingStartedText {
|
||||
font-size: 22px!important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: #3d3e3f;
|
||||
}
|
||||
@@ -123,13 +154,6 @@ body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.onPageNav .toc-headings > li > a.active,
|
||||
.toc .toggleNav ul li.navListItemActive a,
|
||||
.toc .toggleNav ul li a:hover,
|
||||
.toc .toggleNav ul li a:focus {
|
||||
font-family: 'Calibre Regular', sans-serif;
|
||||
}
|
||||
|
||||
.imageAlignTop .blockImage {
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -322,7 +346,7 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
@media only screen and (max-width: 1023px) {
|
||||
.gridBlock .twoByGridBlock img,
|
||||
.gridBlock .threeByGridBlock img,
|
||||
.gridBlock .fourByGridBlock img {
|
||||
@@ -382,6 +406,10 @@ a {
|
||||
.navigationSlider .slidingNav ul li {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.projectTitle {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 880px) {
|
||||
@@ -506,11 +534,13 @@ ol {
|
||||
width: 1100px;
|
||||
padding: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNavVisible.separateOnPageNav .fixedHeaderContainer {
|
||||
margin-top: 35px;
|
||||
width: 100vw;
|
||||
}
|
||||
@media only screen and (min-width: 1000px) {
|
||||
.sideNavVisible.separateOnPageNav .fixedHeaderContainer {
|
||||
margin-top: 35px;
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
.sideNavVisible .navigationSlider .slidingNav ul li a {
|
||||
@@ -525,8 +555,23 @@ ol {
|
||||
top: 4px;
|
||||
left: 5px;
|
||||
font-size: 30px;
|
||||
color: #ffe800;
|
||||
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
-moz-transition: all 0.2s ease-in-out;
|
||||
-o-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.yellowArrow:hover {
|
||||
color: #ffe800;
|
||||
|
||||
-webkit-transform: translateX(5px);
|
||||
-moz-transform: translateX(5px);
|
||||
-ms-transform: translateX(5px);
|
||||
-o-transform: translateX(5px);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
.blockTitleText {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 806 B |
BIN
doc/website/static/img/logo.png
Normal file
BIN
doc/website/static/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user