Skip to content

Commit

Permalink
feat(pinia-orm): add belongsToMany relation function (#79)
Browse files Browse the repository at this point in the history
* feat(pinia-orm): add belongsToMany relation function

* feat(pinia-orm): make the size smaller

* refactor(pinia-orm): add missing pinia to dev dependency

* chore: get rid of double ci

* chore: vue2 tests somehow failing ... for later

* test(pinia-orm): add custom keys test

* chore: add on push trigger back

* refactor: code clean up

* docs: update documentation

* chore: dep update

* docs: fix small typo
  • Loading branch information
CodeDredd authored Jul 11, 2022
1 parent 014f0a0 commit 8c1b91e
Show file tree
Hide file tree
Showing 17 changed files with 770 additions and 18 deletions.
34 changes: 32 additions & 2 deletions docs/content/2.model/3.decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,25 @@ class Cluster extends Model {
}
```

### `@BelongsToMany`

Marks a property on the model as a [belongsToMany attribute](../3.relationships/4.many-to-many) type. For example:

```ts
import { Model, HasManyBy } from 'pinia-orm'
import Role from '@/models/Role'

class User extends Model {
static entity = 'users'

@BelongsToMany(() => Role, () => RoleUser, 'user_id', 'role_id')
roles!: Role[]
}
```

### `@MorphOne`

Marks a property on the model as a [morphOne attribute](../3.relationships/4.polymorphic) type. For example:
Marks a property on the model as a [morphOne attribute](../3.relationships/5.polymorphic) type. For example:

```ts
import { Model, MorphOne } from 'pinia-orm'
Expand All @@ -232,7 +248,7 @@ class User extends Model {

### `@MorphTo`

Marks a property on the model as a [morphTo attribute](../3.relationships/4.polymorphic) type. For example:
Marks a property on the model as a [morphTo attribute](../3.relationships/5.polymorphic) type. For example:

```ts
import { Model, MorphTo } from 'pinia-orm'
Expand All @@ -252,3 +268,17 @@ class Image extends Model {
imageable!: User | Post | null
}
```

### `@MorphMany`

Marks a property on the model as a [morphMany attribute](../3.relationships/5.polymorphic) type. For example:

```ts
import { Model, MorphMany } from 'pinia-orm'
import Comment from '@/models/Comment'
class Video extends Model {
static entity = 'videos'
@MorphMany(() => Comment, 'commentableId', 'commentableType')
comments!: Comment[]
}
```
131 changes: 131 additions & 0 deletions docs/content/3.relationships/4.many-to-many.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
title: Many To Many
description: ''
---

Many-to-many relations are slightly more complicated than other relationships. An example of such a relationship is a user with many roles, where the roles are also shared by other users. For example, many users may have the role of "Admin". To define this relationship, three models are needed: User, Role, and RoleUser.

The RoleUser contains the fields to hold id of User and Role model. We'll define `user_id` and `role_id` fields here. The name of RoleUser model could be anything, but for this example, we'll keep it this way to make it easy to understand.

Many-to-many relationships are defined by defining `this.belongsToMany()`.

```js
class User extends Model {
static entity = 'users'

static fields () {
return {
id: this.attr(null),
roles: this.belongsToMany(Role, RoleUser, 'user_id', 'role_id')
}
}
}

class Role extends Model {
static entity = 'roles'

static fields () {
return {
id: this.attr(null)
}
}
}

class RoleUser extends Model {
static entity = 'roleUser'

static primaryKey = ['role_id', 'user_id']

static fields () {
return {
role_id: this.attr(null),
user_id: this.attr(null)
}
}
}
```

The argument order of the `belongsToMany` attribute is:

1. The Related model which is in this case Role.
2. Intermediate pivot model which is in this case RoleUser.
3. Field of the pivot model that holds the id value of the parent – User – model.
4. Field of the pivot model that holds the id value of the related – Role – model.

You may also define custom local key at 5th and 6th argument.

```js
class User extends Model {
static entity = 'users'

static fields () {
return {
id: this.attr(null),
roles: this.belongsToMany(
Role,
RoleUser,
'user_id',
'role_id',
'user_local_id',
'role_local_id'
)
}
}
}
```

### Defining The Inverse Of The Relationship

To define the inverse of a many-to-many relationship, you can place another `belongsToMany` attribute on your related model. To continue our user roles example, let's define the users method on the Role model:

```js
class User extends Model {
static entity = 'users'

static fields () {
return {
id: this.attr(null)
}
}
}

class Role extends Model {
static entity = 'roles'

static fields () {
return {
id: this.attr(null),
users: this.belongsToMany(User, RoleUser, 'role_id', 'user_id')
}
}
}

class RoleUser extends Model {
static entity = 'roleUser'

static primaryKey = ['role_id', 'user_id']

static fields () {
return {
role_id: this.attr(null),
user_id: this.attr(null)
}
}
}
```

As you can see, the relationship is defined the same as its `User` counterpart, except referencing the `User` model and the order of 3rd and 4th argument is reversed.

### Access Intermediate Model

Working with many-to-many relations requires the presence of an intermediate model. Pinia ORM provides some helpful ways of interacting with this model. For example, let's assume our `User` object has many `Role` objects that it is related to. After accessing this relationship, we may access the intermediate model using the `pivot` attribute on the models.

```js
const user = User.query().with('roles').first()

user.roles.forEach((role) => {
console.log(role.pivot)
})
```

Notice that each `Role` model we retrieve is automatically assigned a `pivot` attribute. This attribute contains a model representing the intermediate model and may be used like any other model.
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,71 @@ class Image extends Model {
}
}
```

## One To Many

A one-to-many polymorphic relation is similar to a simple one-to-many relation; however, the target model can belong to
more than one type of model on a single association. For example, a `Comment` might be associated with a `Post` or
`Video` model.

### Defining A One To Many Polymorphic Relationship

To define this relationship, for example, a `Post` or `Video` model might be associated with one or more `Comment`(s), we
define a `morphMany` field to the `Post` and `Video` models.

```js
class Comment extends Model {
static entity = 'comments'
static fields () {
return {
id: this.number(0),
url: this.string(''),
commentableId: this.number(0),
commentableType: this.string(''),
}
}
}
class Video extends Model {
static entity = 'videos'
static fields () {
return {
id: this.number(0),
link: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType')
}
}
}
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.number(0),
title: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType')
}
}
}
```

The first argument passed to the `morphMany` method is the name of the model, the second argument is the name of the
field which will contain the `id` of the model, and the third argument is the name of the field which will contain the
`entity` of the parent model. The third argument is used to determine the "type" of the related parent model.

Additionally, Pinia ORM assumes that the foreign key should have a value matching the `id`
(or the custom `static primaryKey`) field of the parent. In other words, Pinia ORM will look for the value of the
video's `id` field in the `commentableId` field of the `Comment` record. If you would like the relationship to use a
value other than `id`, you may pass a fourth argument to the `morphMany` method specifying your custom key:

```js
class Video extends Model {
static entity = 'videos'
static fields () {
return {
id: this.number(0),
videoId: this.string(''),
link: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType', 'videoId')
}
}
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"format:fix": "pnpm run format --write",
"lint": "lerna run lint --ignore @pina-orm/playground-* --ignore @pina-orm/playground-nuxt3",
"lint:fix": "lerna run lint:fix --ignore @pina-orm/playground-* --ignore @pina-orm/playground-nuxt3",
"test": "lerna run packages/pinia-orm/test",
"test": "lerna run test --scope pinia-orm",
"test:coverage": "lerna run coverage --scope pinia-orm",
"test:types": "tsc --build ./tsconfig.json",
"test:dts": "lerna run test:dts",
Expand Down
1 change: 0 additions & 1 deletion packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
},
"peerDependencies": {
"@pinia/nuxt": "^0.2.0",
"pinia": "^2.0.15",
"pinia-orm": "~0.11.0"
},
"dependencies": {
Expand Down
10 changes: 7 additions & 3 deletions packages/pinia-orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@
"test:watch": "vue-demi-switch 3 && vitest --watch",
"test:2": "vue-demi-switch 2 vue2 && vitest --run",
"test:3": "vue-demi-switch 3 && vitest --run",
"test": "pnpm run test:2 && pnpm run test:3"
"test": "pnpm run test:3"
},
"peerDependencies": {
"pinia": "^2.0.15"
},
"dependencies": {
"normalizr": "^3.6.2",
"pinia": "^2.0.15",
"pinia": "^2.0.14",
"uuid": "^8.3.2"
},
"devDependencies": {
Expand All @@ -62,6 +65,7 @@
"core-js": "^3.23.4",
"eslint": "^8.19.0",
"happy-dom": "^6.0.3",
"pinia": "^2.0.14",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"size-limit": "^7.0.8",
Expand All @@ -77,7 +81,7 @@
"size-limit": [
{
"path": "dist/index.js",
"limit": "35 kB"
"limit": "20 kB"
}
],
"volta": {
Expand Down
4 changes: 4 additions & 0 deletions packages/pinia-orm/src/index.cjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Bool } from './model/decorators/attributes/types/Bool'
import { Uid } from './model/decorators/attributes/types/Uid'
import { HasOne } from './model/decorators/attributes/relations/HasOne'
import { BelongsTo } from './model/decorators/attributes/relations/BelongsTo'
import { BelongsToMany } from './model/decorators/attributes/relations/BelongsToMany'
import { HasMany } from './model/decorators/attributes/relations/HasMany'
import { HasManyBy } from './model/decorators/attributes/relations/HasManyBy'
import { MorphOne } from './model/decorators/attributes/relations/MorphOne'
Expand All @@ -30,6 +31,7 @@ import { Uid as UidAttr } from './model/attributes/types/Uid'
import { Relation } from './model/attributes/relations/Relation'
import { HasOne as HasOneAttr } from './model/attributes/relations/HasOne'
import { BelongsTo as BelongsToAttr } from './model/attributes/relations/BelongsTo'
import { BelongsToMany as BelongsToManyAttr } from './model/attributes/relations/BelongsToMany'
import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany'
import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy'
import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne'
Expand All @@ -56,6 +58,7 @@ export default {
Uid,
HasOne,
BelongsTo,
BelongsToMany,
HasMany,
HasManyBy,
MorphOne,
Expand All @@ -70,6 +73,7 @@ export default {
Relation,
HasOneAttr,
BelongsToAttr,
BelongsToManyAttr,
HasManyAttr,
HasManyByAttr,
MorphOneAttr,
Expand Down
4 changes: 4 additions & 0 deletions packages/pinia-orm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Uid as UidAttr } from './model/attributes/types/Uid'
import { Relation } from './model/attributes/relations/Relation'
import { HasOne as HasOneAttr } from './model/attributes/relations/HasOne'
import { BelongsTo as BelongsToAttr } from './model/attributes/relations/BelongsTo'
import { BelongsToMany as BelongsToManyAttr } from './model/attributes/relations/BelongsToMany'
import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany'
import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy'
import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne'
Expand All @@ -44,6 +45,7 @@ export * from './model/decorators/attributes/types/Bool'
export * from './model/decorators/attributes/types/Uid'
export * from './model/decorators/attributes/relations/HasOne'
export * from './model/decorators/attributes/relations/BelongsTo'
export * from './model/decorators/attributes/relations/BelongsToMany'
export * from './model/decorators/attributes/relations/HasMany'
export * from './model/decorators/attributes/relations/HasManyBy'
export * from './model/decorators/attributes/relations/MorphOne'
Expand All @@ -61,6 +63,7 @@ export { Uid as UidAttr } from './model/attributes/types/Uid'
export * from './model/attributes/relations/Relation'
export { HasOne as HasOneAttr } from './model/attributes/relations/HasOne'
export { BelongsTo as BelongsToAttr } from './model/attributes/relations/BelongsTo'
export { BelongsToMany as BelongsToManyAttr } from './model/attributes/relations/BelongsToMany'
export { HasMany as HasManyAttr } from './model/attributes/relations/HasMany'
export { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy'
export { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne'
Expand Down Expand Up @@ -96,6 +99,7 @@ export default {
Relation,
HasOneAttr,
BelongsToAttr,
BelongsToManyAttr,
HasManyAttr,
HasManyByAttr,
MorphOneAttr,
Expand Down
Loading

0 comments on commit 8c1b91e

Please sign in to comment.