Skip to content

Add association API to factory builder #28

@davidtkramer

Description

@davidtkramer

Need to figure out an API for handling associations between entity types. Some options:

interface User {
  id: number;
  name: string;
  posts: Array<Post>
  tenantId: number;
}

interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

// Option A
const userFactory = createFactory((factory) =>
  factory
    .attributes<User>({
      id: 1,
      name: 'Alice',
      tenantId: 1,
      posts: () => []
    })
    .hasOne('tenant')
    .hasMany('posts')
);

const postFactory = createFactory((factory) => 
  factory
    .attributes<Post>({
      id: 1,
      userId: 1,
      title: 'title',
      body: 'body',
    })
    .hasOne('user')
);

const tenant = db.tenants.create();
const user = db.users.create({ tenant }) // sets tenantId on user
const posts = db.posts.createList(3, { user }); // sets userId on posts (sets posts on user or no?)

// Option B
// This would be awkward because attribute builders aren't invoked when the definition is evaluated.
// Would have to iterate through each attribute builder and invoke it to see if it's an association.
// Advantage is that this could work with attributes-only notation
const userFactory = createFactory((factory) =>
  factory
    .attributes<User>({
      id: 1,
      name: 'Alice',
      tenantId: ({ hasOne }) => hasOne('tenant'),
      posts: ({ hasMany }) => hasMany('posts')
    })
);

const postFactory = createFactory((factory) => 
  factory
    .attributes<Post>({
      id: 1,
      userId: ({ hasOne }) => hasOne('user'),
      title: 'title',
      body: 'body',
    })
);

// Option C
const userFactory = createFactory((factory) =>
  factory
    .attributes<User>({
      id: 1,
      name: 'Alice',
      tenantId: factory.hasOne('tenant'),
      posts: factory.hasMany('posts')
    })
);

const postFactory = createFactory((factory) => 
  factory
    .attributes<Post>({
      id: 1,
      userId: factory.hasOne('user'),
      title: 'title',
      body: 'body',
    })
);

// Option D
// - Doesn't feel right to break away from the builder pattern, could be confusing
//   to mix dsl patterns
// - Terseness is nice
// - Works with builder and attribute notation
import { createFactory, hasOne, hasMany } from 'typical-data';

const userFactory = createFactory<User>({
  id: 1,
  name: 'Alice',
  tenantId: hasOne('tenant'),
  posts: hasMany('posts')
});

const postFactory = createFactory<Post>({
  id: 1,
  userId: hasOne('user'),
  title: 'title',
  body: 'body',
});

// Option E
const userFactory = createFactory('user', (factory) =>
  factory
    .attributes<User>(({ primaryKey, hasMany, hasOne }) => ({
      id: primaryKey(),
      name: 'Alice',
      posts: hasMany('post')
    }))
);

const postFactory = createFactory('post', (factory) => 
  factory
    .attributes<Post>(({ primaryKey, hasOne }) => ({
      id: primaryKey(),
      author: hasOne('user'),
      title: 'title',
      body: 'body',
    }))
);

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions