Skip to content

Commit

Permalink
docs: tag v4 version in docs
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Sep 8, 2020
1 parent 39c7ed8 commit 9a47ad2
Show file tree
Hide file tree
Showing 52 changed files with 8,271 additions and 6 deletions.
9 changes: 3 additions & 6 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ module.exports = {
algolia: {
apiKey: '26fadcd97750a33cd8081a07dda2c0cf',
indexName: 'mikro-orm',
// algoliaOptions: { filters: `version:${versions[0]}` }, // TODO uncomment when v4 is released
algoliaOptions: { filters: `version:3.6` },
algoliaOptions: { filters: `version:${versions[0]}` },
},
navbar: {
title: '',
Expand All @@ -34,8 +33,7 @@ module.exports = {
links: [
{
to: 'versions',
// label: `${pkg.version}`, // TODO uncomment when v4 is released
label: '3.6.15',
label: `${pkg.version}`,
position: 'left',
'data-type': 'versions',
},
Expand Down Expand Up @@ -70,8 +68,7 @@ module.exports = {
{ label: 'Installation & Usage', to: 'docs/installation' },
{ label: 'Quick Start', href: 'https://github.com/mikro-orm/mikro-orm#-quick-start' },
{ label: 'Migration from v3 to v4', to: 'docs/upgrading-v3-to-v4' },
// { label: 'Version 3.6 docs', to: 'docs/3.6/installation' }, // TODO uncomment when v4 is released
{ label: 'Version 3.6 docs', to: 'docs/installation' },
{ label: 'Version 3.6 docs', to: 'docs/3.6/installation' },
],
},
{
Expand Down
169 changes: 169 additions & 0 deletions docs/versioned_docs/version-4.0/cascading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Cascading persist, merge and remove
sidebar_label: Cascading
---

When persisting or removing entity, all your references are by default cascade persisted.
This means that by persisting any entity, ORM will automatically persist all of its
associations.

You can control this behaviour via `cascade` attribute of `@ManyToOne`, `@ManyToMany`,
`@OneToMany` and `@OneToOne` fields.

> New entities without primary key will be always persisted, regardless of `cascade` value.
```typescript
// cascade persist & merge is default value
@OneToMany({ entity: () => Book, mappedBy: 'author' })
books = new Collection<Book>(this);

// same as previous definition
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [Cascade.PERSIST, Cascade.MERGE] })
books = new Collection<Book>(this);

// only cascade remove
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [Cascade.REMOVE] })
books = new Collection<Book>(this);

// cascade persist and remove (but not merge)
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [Cascade.PERSIST, Cascade.REMOVE] })
books = new Collection<Book>(this);

// no cascade
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [] })
books = new Collection<Book>(this);

// cascade all (persist, merge and remove)
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [Cascade.ALL] })
books = new Collection<Book>(this);

// same as previous definition
@OneToMany({ entity: () => Book, mappedBy: 'author', cascade: [Cascade.PERSIST, Cascade.MERGE, Cascade.REMOVE] })
books = new Collection<Book>(this);
```

## Cascade persist

Here is example of how cascade persist works:

```typescript
const book = await orm.em.findOne(Book, 'id', ['author', 'tags']);
book.author.name = 'Foo Bar';
book.tags[0].name = 'new name 1';
book.tags[1].name = 'new name 2';
await orm.em.persistAndFlush(book); // all book tags and author will be persisted too
```

> When cascade persisting collections, keep in mind only fully initialized collections
> will be cascade persisted.
## Cascade merge

When you want to merge entity and all its associations, you can use `Cascade.MERGE`. This
comes handy when you want to clear identity map (e.g. when importing large number of entities),
but you also have to keep your parent entities managed (because otherwise they would be considered
as new entities and insert-persisted, which would fail with non-unique identifier).

In following example, without having `Author.favouriteBook` set to cascade merge, you would
get an error because it would be cascade-inserted with already taken ID.

```typescript
const a1 = new Author(...);
a1.favouriteBook = new Book('the best', ...);
await orm.em.persistAndFlush(a1); // cascade persists favourite book as well

for (let i = 1; i < 1000; i++) {
const book = new Book('...', a1);
orm.em.persist(book);

// persist every 100 records
if (i % 100 === 0) {
await orm.em.flush();
orm.em.clear(); // this makes both a1 and his favourite book detached
orm.em.merge(a1); // so we need to merge them to prevent cascade-inserts

// without cascade merge, you would need to manually merge all his associations
orm.em.merge(a1.favouriteBook); // not needed with Cascade.MERGE
}
}

await orm.em.flush();
```

## Cascade remove

Cascade remove works same way as cascade persist, just for removing entities. Following
example assumes that `Book.publisher` is set to `Cascade.REMOVE`:

> Note that cascade remove for collections can be inefficient as it will fire 1 query
> for each entity in collection.
```typescript
await orm.em.removeEntity(book); // this will also remove book.publisher
```

Keep in mind that cascade remove **can be dangerous** when used on `@ManyToOne` fields,
as cascade removed entity can stay referenced in another entities that were not removed.

```typescript
const publisher = new Publisher(...);
// all books with same publisher
book1.publisher = book2.publisher = book3.publisher = publisher;
await orm.em.removeEntity(book1); // this will remove book1 and its publisher

// but we still have reference to removed publisher here
console.log(book2.publisher, book3.publisher);
```

## Orphan removal

In addition to `Cascade.REMOVE`, there is also additional and more aggressive remove
cascading mode which can be specified using the `orphanRemoval` flag of the `@OneToOne`
and `@OneToMany` properties:

```typescript
@Entity()
export class Author {

@OneToMany({ entity: () => Book, mappedBy: 'author', orphanRemoval: true })
books = new Collection<Book>(this);

}
```

> `orphanRemoval` flag behaves just like `Cascade.REMOVE` for remove operation, so specifying
> both is redundant.
With simple `Cascade.REMOVE`, you would need to remove the `Author` entity to cascade
the operation down to all loaded `Book`s. By enabling orphan removal on the collection,
`Book`s will be also removed when they get disconnected from the collection (either via
`remove()`, or by replacing collection items via `set()`):

```typescript
await author.books.set([book1, book2]); // replace whole collection
await author.books.remove(book1); // remove book from collection
await orm.em.persistAndFlush(author); // book1 will be removed, as well as all original items (before we called `set()`)
```

In this example, no `Book` would be removed with simple `Cascade.REMOVE` as no remove operation
was executed.

## Declarative Referential Integrity

> This is only supported in SQL drivers.
As opposed to the application level cascading controlled by the `cascade` option, we can
also define database level referential integrity actions: `on update` and `on delete`.

Their values are automatically inferred from the `cascade` option value. You can also
control the value manually via `onUpdateIntegrity` and `onDelete` options.

```typescript
@Entity()
export class Book {

@ManyToOne({ onUpdateIntegrity: 'set null', onDelete: 'cascade' })
author?: Author;

}
```
192 changes: 192 additions & 0 deletions docs/versioned_docs/version-4.0/collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
---
title: Collections
---

`OneToMany` and `ManyToMany` collections are stored in a `Collection` wrapper. It implements
iterator so you can use `for of` loop to iterate through it.

Another way to access collection items is to use bracket syntax like when you access array items.
Keep in mind that this approach will not check if the collection is initialed, while using `get`
method will throw error in this case.

> Note that array access in `Collection` is available only for reading already loaded items, you
> cannot add new items to `Collection` this way.
```typescript
const author = orm.em.findOne(Author, '...', ['books']); // populating books collection

// or we could lazy load books collection later via `init()` method
await author.books.init();

for (const book of author.books) {
console.log(book.title); // initialized
console.log(book.author.isInitialized()); // true
console.log(book.author.id);
console.log(book.author.name); // Jon Snow
console.log(book.publisher); // just reference
console.log(book.publisher.isInitialized()); // false
console.log(book.publisher.id);
console.log(book.publisher.name); // undefined
}

// collection needs to be initialized before you can work with it
author.books.add(book);
console.log(author.books.contains(book)); // true
author.books.remove(book);
console.log(author.books.contains(book)); // false
author.books.add(book);
console.log(author.books.count()); // 1
author.books.removeAll();
console.log(author.books.contains(book)); // false
console.log(author.books.count()); // 0
console.log(author.books.getItems()); // Book[]
console.log(author.books.getIdentifiers()); // array of string | number
console.log(author.books.getIdentifiers('_id')); // array of ObjectId

// array access works as well
console.log(author.books[1]); // Book
console.log(author.books[12345]); // undefined, even if the collection is not initialized

const author = orm.em.findOne(Author, '...'); // books collection has not been populated
console.log(author.books.getItems()); // throws because the collection has not been initialized
// initialize collection if not already loaded and return its items as array
console.log(await author.books.loadItems()); // Book[]
```

## OneToMany Collections

`OneToMany` collections are inverse side of `ManyToOne` references, to which they need to point via `fk` attribute:

```typescript
@Entity()
export class Book {

@PrimaryKey()
_id!: ObjectId;

@ManyToOne()
author!: Author;

}

@Entity()
export class Author {

@PrimaryKey()
_id!: ObjectId;

@OneToMany(() => Book, book => book.author)
books1 = new Collection<Book>(this);

// or via options object
@OneToMany({ entity: () => Book, mappedBy: 'author' })
books2 = new Collection<Book>(this);

}
```

## ManyToMany Collections

For ManyToMany, SQL drivers use pivot table that holds reference to both entities.

As opposed to them, with MongoDB we do not need to have join tables for `ManyToMany`
relations. All references are stored as an array of `ObjectId`s on owning entity.

### Unidirectional

Unidirectional `ManyToMany` relations are defined only on one side, if you define only `entity`
attribute, then it will be considered the owning side:

```typescript
@ManyToMany(() => Book)
books1 = new Collection<Book>(this);

// or mark it as owner explicitly via options object
@ManyToMany({ entity: () => Book, owner: true })
books2 = new Collection<Book>(this);
```

### Bidirectional

Bidirectional `ManyToMany` relations are defined on both sides, while one is owning side (where references are store),
marked by `inversedBy` attribute pointing to the inverse side:

```typescript
@ManyToMany(() => BookTag, tag => tag.books, { owner: true })
tags = new Collection<BookTag>(this);

// or via options object
@ManyToMany({ entity: () => BookTag, inversedBy: 'books' })
tags = new Collection<BookTag>(this);
```

And on the inversed side we define it with `mappedBy` attribute pointing back to the owner:

```typescript
@ManyToMany(() => Book, book => book.tags)
books = new Collection<Book>(this);

// or via options object
@ManyToMany({ entity: () => Book, mappedBy: 'tags' })
books = new Collection<Book>(this);
```

### Forcing fixed order of collection items

> Since v3 many to many collections does not require having auto increment primary key, that
> was used to ensure fixed order of collection items.
To preserve fixed order of collections, you can use `fixedOrder: true` attribute, which will
start ordering by `id` column. Schema generator will convert the pivot table to have auto increment
primary key `id`. You can also change the order column name via `fixedOrderColumn: 'order'`.

You can also specify default ordering via `orderBy: { ... }` attribute. This will be used when
you fully populate the collection including its items, as it orders by the referenced entity
properties instead of pivot table columns (which `fixedOrderColumn` is). On the other hand,
`fixedOrder` is used to maintain the insert order of items instead of ordering by some property.

## Propagation of Collection's add() and remove() operations

When you use one of `Collection.add()` method, the item is added to given collection,
and this action is also propagated to its counterpart.

```typescript
// one to many
const author = new Author(...);
const book = new Book(...);

author.books.add(book);
console.log(book.author); // author will be set thanks to the propagation
```

For M:N this works in both ways, either from owning side, or from inverse side.

```typescript
// many to many works both from owning side and from inverse side
const book = new Book(...);
const tag = new BookTag(...);

book.tags.add(tag);
console.log(tag.books.contains(book)); // true

tag.books.add(book);
console.log(book.tags.contains(tag)); // true
```

> Collections on both sides have to be initialized, otherwise propagation won't work.
> Although this propagation works also for M:N inverse side, you should always use owning
> side to manipulate the collection.
Same applies for `Collection.remove()`.

## Filtering and ordering of collection items

When initializing collection items via `collection.init()`, you can filter the collection
as well as order its items:

```typescript
await book.tags.init({ where: { active: true }, orderBy: { name: QueryOrder.DESC } });
```

> You should never modify partially loaded collection.
Loading

0 comments on commit 9a47ad2

Please sign in to comment.