Skip to content

Commit

Permalink
docs: more exmaples about typescript support (#405)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyjake authored Nov 7, 2023
1 parent 4f78f67 commit dc9056b
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 6 deletions.
114 changes: 114 additions & 0 deletions docs/associations.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ Leoric supports four types of associations:

Associations can be declared within the `Model.describe()` method. For example, by declaring a shop `belongsTo()` its owner, you're telling Leoric that when `Shop.find().with('owner')`, Leoric should join the table of owners, load the data, and instantiate `shop.owner` on the found objects.

There are four equivalent decorators for projects written in TypeScript

- `@BelongsTo()`
- `@HasMany()`
- `@HasMany({ through })`
- `@HasOne()`

The major difference between static method and decorators for associations is that the first parameter can be omitted in the decorator equivalent. For example, `Post.belongsTo('user')` declared with decorator is like below:

```ts
class Post {
@BelongsTo()
user: User
}
```

### `belongsTo()`

<figure class="belongs-to-erd">
Expand All @@ -53,6 +69,15 @@ class Item extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Item extends Bone {
@BelongsTo()
shop: Shop;
}
```

Leoric locates the model class `Shop` automatically by capitalizing `shop` as the model name. If that's not the case, we can specify the model name explicitly by passing `className`:

```js
Expand All @@ -63,6 +88,15 @@ class Item extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Item extends Bone {
@BelongsTo({ className: 'Seller' })
shop: Shop;
}
```

> Please be noted that the value passed to `className` is a string rather than the actual model class. Tossing the actual classes back and forth between the two parties of an association at `Model.describe()` phase can be error prone because it causes cyclic dependencies.
As you can tell from the ER diagram, the foreign key used to associate a `belongsTo()` relationship is located on the model that initiates it. The name of the foreign key is found by uncapitalizing the target model's name and then appending an `Id`. In this case, the foerign key is converted from `Shop` to `shopId`.
Expand All @@ -79,6 +113,15 @@ class Item extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Item extends Bone {
@BelongsTo({ foreignKey: 'sellerId' })
shop: Shop;
}
```

### `hasMany()`

<figure class="has-many-erd">
Expand All @@ -95,6 +138,15 @@ class Shop extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
@HasMany()
items: Item[];
}
```

> Please be noted that unlike `belongsTo()`, the name passed to `hasMany()` is usually in plural.
The way Leoric locates the actual model class is quite similar. It starts with singularizing the name, then capitalizing. In this case, `items` get singularized to `item`, and then `Item` is used to look for the actual model class.
Expand All @@ -109,6 +161,16 @@ class Shop extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
// It might be able to deduce the className from `Commodify[]` type
@HasMany({ className: 'Commodity' })
items: Commodity[];
}
```

As you can tell from the ER diagram, the foreign key used to join two tables is located at the target table, `items`. To override the foreign key, just pass it to the option of `hasMany()`:

```js
Expand All @@ -119,6 +181,16 @@ class Shop extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
@HasMany({ foreignKey: 'sellerId' })
items: Item[];
}
```


### `hasMany({ through })`

The world of entity relationships doesn't consist of one-to-one or one-to-many associations only. There are many scenarios that require a many-to-many association to be setup. However, in relational databases many-to-many between two tables isn't possible by nature. To accompish this, we need to introduce an intermediate table to bridge the associations.
Expand All @@ -145,6 +217,18 @@ class Shop extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
@HasMany({ foreignKey: 'targetId', where: { targetType: 0 } })
tagMaps: TagMap[];

@HasMany({ through: 'tagMaps' })
tags: Tag[];
}
```

On `Tag`'s side:

```js
Expand All @@ -156,6 +240,18 @@ class Tag extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
@HasMany({ className: 'TagMap', foreignKey: 'targetId', where: { targetType: 0 } })
shopTagMaps: TagMap[];

@HasMany({ through: 'shopTagMaps' })
shops: Tag[];
}
```

If suddenly our business requires us to apply the tag system to items too, the changes needed on `Tag` model is trivial:

```diff
Expand Down Expand Up @@ -191,6 +287,15 @@ class User extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class User extends Bone {
@HasOne({ foreignKey: 'ownerId' })
shop: Shop;
}
```

And the shop belongs to the user:

```js
Expand All @@ -201,6 +306,15 @@ class Shop extends Bone {
}
```

The TypeScript equivalent with decorator is like below:

```ts
class Shop extends Bone {
@BelongsTo({ className: 'User' })
owner: User;
}
```

### Choosing Between `belongsTo()` and `hasOne()`

As dicussed in the `hasOne()` section, the difference between `belongsTo()` and `hasOne()` is mostly at where to place the foreign key. The corresponding model of the table that contains the foreign key should be the one that declares the `belongsTo()` association.
Expand Down
9 changes: 8 additions & 1 deletion docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,14 @@ class Shop extends Bone {
}
```

A lot of schema settings can be done within the `static initialize()` method. We'll get to that later.
A lot of schema settings can be done within the `static initialize()` method. We'll get to that later. For TypeScript projects this static method is unnecessary, most of the settings can be tweaked with the equivalent decorators. The example above can be refactored with decorator like below:

```ts
class Shop extends Bone {
@Column({ name: 'removed_at' })
deltedAt: Date;
}
```

## Connecting Models to Database

Expand Down
15 changes: 14 additions & 1 deletion docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Here is the list of options supported by `@Column()` that can be used to customi
| primaryKey = false | declare class field as the primary key |
| autoIncrement = false | enable auto increment on corresponding class field, must be numeric type |
| allowNull = true | class field can not be null when persisting to database |
| type = typeof field | override the data type deduced from class field type |
| type = typeof field | override the data type deduced from class field type |
| name = string | actual name of the table field in database |

If `type` option is omitted, `@Column()` will try to deduce the corresponding one as below:

Expand All @@ -53,6 +54,18 @@ If `type` option is omitted, `@Column()` will try to deduce the corresponding on
| bigint | BIGINT |
| boolean | BOOLEAN / TINYINT(1) |

Here is an example that is a little bit more comprehensive:

```ts
class User extends Bone {
@Column({ name: 'ssn', primaryKey: true, type: VARCHAR(16) })
ssn: string;

@Column({ name: 'gmt_create', allowNull: false })
createdAt: Date;
}
```

### BelongsTo

```ts
Expand Down
Loading

0 comments on commit dc9056b

Please sign in to comment.