Skip to content

Commit

Permalink
Add Adapter and Strategy docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Noah Bogart committed Feb 10, 2021
1 parent fb6efa9 commit 883e6a7
Show file tree
Hide file tree
Showing 11 changed files with 6,253 additions and 7,080 deletions.
53 changes: 21 additions & 32 deletions docs/api/README.md
@@ -1,5 +1,6 @@
---
sidebar: auto
title: API
---

# Exports
Expand All @@ -8,44 +9,32 @@ sidebar: auto

```typescript
import {
// primary export
fr,
FixtureRiveter,

// ORM adapters
Adapter,
DefaultAdapter,
ObjectionAdapter,
SequelizeAdapter,

// object creation strategies
Strategy,
AttributesForStrategy,
BuildStrategy,
CreateStrategy,
// primary export
fr,
FixtureRiveter,

// ORM adapters
Adapter,
DefaultAdapter,
ObjectionAdapter,
SequelizeAdapter,

// object creation strategies
Strategy,
AttributesForStrategy,
BuildStrategy,
CreateStrategy,
} from "fixture-riveter";
```

## `fr` and `FixtureRiveter`

The primary method for consuming the library. (The `FixtureRiveter` constructor is exposed as well, in case you need to instantiate it yourself). The functionality is discussed further in [fr api](./fr/README.md).
The primary method for consuming the library. (The `FixtureRiveter` constructor is exposed as well, in case you need to instantiate it yourself). A full description of the internals can be found in [fixture-riveter](./fr/README.md).

### Relative links
## `Adapter`

[in parent directory](../README.md)
`Adapter` is the interface that `fixture-riveter` uses to interact with 1) a given fixture's class, and 2) the database (or ORM). It defines 5 methods that all subclasses must implement. A description of these methods, and instructions on writing your own, can be found in [Adapter](./adapter.md).

[in same directory](./adapter.md)
## `Strategy`

[in child directory](./exports/README.md)

[in adjacent directory](../guide/README.md)

### Absolute links

[in parent directory](/fixture-riveter/api/README.md)

[in same directory](/fixture-riveter/api/adapter.md)

[in child directory](/fixture-riveter/api/exports/README.md)

[in adjacent directory](/fixture-riveter/api/guide/README.md)
`Strategy` is the interface that `fixture-riveter` uses to turn fixture definitions into objects, whether it be Plain Old Javascript Objects or instances of a given class. A description of its methods, and instructions on writing one's own, can be found in [Strategy](./strategy.md).
85 changes: 68 additions & 17 deletions docs/api/adapter.md
@@ -1,4 +1,9 @@
## `Adapter`
---
sidebar: auto
title: Adapter
---

# `Adapter`

Instead of writing ORM-specific code for instancing and persisting created objects, we rely on this interface (and the following default implementation of it). This handles the two aforementioned aspects of [factory_bot][factory_bot]: creating an instance of a given model, and then persisting it to the database. (There are other reasons to use it, but that's less important.)

Expand All @@ -12,29 +17,38 @@ The `DefaultAdapter` has barebones implementations of each of the interface's fu
import {fr, DefaultAdapter} from "fixture-riveter";

export class UserAdapter extends DefaultAdapter {
// This model needs `isNew` set before any fields are set.
build<T>(Model: any): T {
const instance = new Model();
instance.isNew = true;
return instance;
}
// This model needs `isNew` set before any fields are set.
build<T>(Model: any): T {
const instance = new Model();
instance.isNew = true;
return instance;
}
}

fr.setAdapter(new UserAdapter(), "user");
```

### `Adapter` methods
## `Adapter` methods

None of the methods on an `Adapter` will be exposed to the user; they are called internally at various points in the generation of a given fixture. Therefore, the examples below are merely to demonstrate how the function works and will use the `DefaultAdapter`'s implementations.
None of the methods on an `Adapter` will be exposed to the user; they are called internally at various points in the generation of a given fixture. Therefore, the examples below will use the `DefaultAdapter` to demonstrate how the methods work.

#### build
### build()

Called to create an instance of the fixture's Model class. Unless specific arguments are required, `DefaultAdapter`'s implementation is generally good enough.

```typescript
export class DefaultAdapter implements Adapter {
// ...
build<T>(Model: any): T {
return new Model();
}
// ...
```
##### Arguments
| Argument | Type | Description | Optional? |
|----------|----------------|----------------------------------------------------------------------------------|-----------|
| Argument | Type | Description | Optional? |
|----------|----------------|----------------------------------|-----------|
| Model | Class function | The class function (constructor) | Required |
##### Return value
Expand All @@ -53,10 +67,19 @@ user instanceof User
// true
```
#### save
### save()
Called to persist the instance to the database. Must return the persisted instance, not the parameter instance (if there is a difference). Accepts the class function to allow for static methods on the class to handle persistence (for example, Objection.js).
```typescript
export class DefaultAdapter implements Adapter {
// ...
async save<T>(instance: any): Promise<T> {
return instance.save();
}
// ...
```
##### Arguments
| Argument | Type | Description | Optional? |
Expand All @@ -83,10 +106,19 @@ user.id
// 1
```
#### destroy
### destroy()
Called to delete or remove the instance from the database. Must gracefully handle if the instance has not been persisted to the database (for instance, the instance was constructed with `fr.build`, not `fr.create`). Accepts the class function to allow for static methods on the class to handle deletion (for example, Objection.js).
```typescript
export class DefaultAdapter implements Adapter {
// ...
async destroy(instance: any): Promise<void> {
await instance.destroy();
}
// ...
```
##### Arguments
| Argument | Type | Description | Optional? |
Expand All @@ -112,14 +144,23 @@ await User.query().findById(user.id);
// []
```
#### relate
### relate()
Called to "join" two fixture instances together.
::: warning
TODO: clean me up
:::
```typescript
export class DefaultAdapter implements Adapter {
// ...
async relate(instance: any, name: string, other: any): Promise<any> {
return this.set(instance, name, other);
}
// ...
```
##### Arguments
| Argument | Type | Description | Optional? |
Expand All @@ -140,7 +181,7 @@ TODO: clean me up
```typescript
class User {}
class Post {
user: User;
user: User;
}

const user = new User();
Expand All @@ -152,14 +193,24 @@ post.user === user;
// true
```
#### set
### set()
Called to set a property on a fixture instance. Returns the whole instance just in case???
::: warning
TODO: clean me up
:::
```typescript
export class DefaultAdapter implements Adapter {
// ...
set(instance: any, key: string, value: any): any {
instance[key] = value;
return instance;
}
// ...
```
##### Arguments
(instance: any, key: string, value: any)
Expand Down
Empty file removed docs/api/exports/README.md
Empty file.
112 changes: 112 additions & 0 deletions docs/api/strategy.md
@@ -0,0 +1,112 @@
---
sidebar: auto
title: Strategy
---

# `Strategy`

Strategies are the way for `fixture-riveter` to modularize creation and relation in creating instances of fixture definitions. The interface is small, making subclassing easy.

##### Example

```typescript
import {fr, Strategy, Assembler} from "fixture-riveter";

class JsonStrategy extends Strategy {
// This strategy takes the created instance and turns it into a json string
async result<T>(assembler: Assembler<T>): Promise<string> {
const instance = await assembler.toInstance();
return JSON.stringify(instance);
}
}

fr.registerStrategy("json", JsonStrategy);
```

## Instance properties

### name

String name of this strategy. Used to find the strategy when calling `fr.run`.

### adapter

Instance of the specified [Adapter](./adapter.md) (per-fixture or global).

### fixture-riveter

The currently-running instance. Useful when using `relate` (see below).

## Methods

None of the methods on a `Strategy` will be exposed to the uesr; they are called internally in the generation of a given fixture. Because each `Strategy` can perform wildly different operations, each method will be described at a high level. Then, the 3 provided `Strategies` will be detailed.

### result()

Handles calling the "object creation" methods given by the [Assembler](./assembler.md), awaiting callbacks as necessary. Accepts the Model class function as well, in case

##### Arguments

| Argument | Type | Description | Optional? |
|-----------|-----------------------------|----------------------------------|-----------|
| assembler | [Assembler](./assembler.md) | Assembler instance | Required |
| Model | Class function | The class function (constructor) | Optional |

##### Return value

| Type | Description |
|------------------|-----------------------------------------------|
| Promise\<any\> | Object to be returned directly to the caller. |

### relation()

When a fixture defines a relationship to another fixture, this method is called to perform the creation of the related object.

##### Arguments

| Argument | Type | Description | Optional? |
|--------------------|--------------------------------------------------------------------------------|----------------------------------------------------|-----------|
| fixtureName | string | Fixture name of relation | Required |
| traitsAndOverrides | [string[]] <br> [Record\<string, any\>] <br> [string[], Record\<string, any\>] | A list of traits and/or overrides for the relation | Optional |

##### Return value

| Type | Description |
|----------------|---------------------------------------------------|
| Promise\<any\> | Object to be set on the current fixture instance. |

## Default `Strategies`

### `build`

#### result()

Creates an instance of the fixture's model, assigns attributes, and then performs the "afterBuild" callback.

#### relation()

Calls `fr.run` with the "build" strategy, returning just the instance of the related fixture.

### `create`

#### result()

Creates an instance of the fixture's model and assigns attributes. Performs the "afterBuild" and "beforeCreate" callbacks. Calls the adapter's `save` method (detailed [here](./adapter.md)), and then performs the "afterCreate" callbacks.

#### relation()

Calls `fr.run` with the "create" strategy, returning the saved instance of the related fixture.

### `attributesFor`

#### result()

Assigns the fixture's attributes to a plain javacsript object.

#### relation()

Calls `fr.run` with the "null" strategy, returning `undefined`.

::: danger TODO
Look into whether we even need a null strategy?
:::
9 changes: 5 additions & 4 deletions lib/strategies/attributes-for-strategy.ts
Expand Up @@ -3,11 +3,12 @@ import {Strategy} from "./strategy";
import {Pojo} from "../types";

export class AttributesForStrategy extends Strategy {
async relation(fixtureName: string, traitsAndOverrides: any[]): Promise<any> {
return this.fixtureRiveter.run(fixtureName, "null", traitsAndOverrides);
}

async result<T>(assembler: Assembler<T>): Promise<Pojo> {
return assembler.toObject();
}

async relation(fixtureName: string, traitsAndOverrides: any[]): Promise<any> {
// TODO: Can we just return undefined here and remove the null strategy?
return this.fixtureRiveter.run(fixtureName, "null", traitsAndOverrides);
}
}
8 changes: 4 additions & 4 deletions lib/strategies/build-strategy.ts
Expand Up @@ -3,14 +3,14 @@ import {Strategy} from "./strategy";

/* eslint-disable class-methods-use-this */
export class BuildStrategy extends Strategy {
async relation(fixtureName: string, traitsAndOverrides: any[]): Promise<any> {
return this.fixtureRiveter.run(fixtureName, "build", traitsAndOverrides);
}

async result<T>(assembler: Assembler<T>): Promise<T> {
const instance = await assembler.toInstance();
await assembler.runCallbacks("afterBuild", instance);

return instance;
}

async relation(fixtureName: string, traitsAndOverrides: any[]): Promise<any> {
return this.fixtureRiveter.run(fixtureName, "build", traitsAndOverrides);
}
}

0 comments on commit 883e6a7

Please sign in to comment.