Skip to content
This repository has been archived by the owner on Dec 20, 2019. It is now read-only.
/ data-models Public archive

Abstract data models with first class support for relationships

License

Notifications You must be signed in to change notification settings

poppinss/data-models

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Data models

Data models to manage data in OOP style with first class support for relationships

circleci-image npm-image license-image

This library is abstracted from AdonisJs Lucid ORM to work as a standalone implementation of Active model heavily inspired by Rails.

The consumer of this library will be a developer creating an ORM for backend or frontend and implementing adapter for fetching and persisting data.

Table of contents

How it works

Let's say you are working with relational data in your app and want clean abstractions to fetch and save that data. For example:

import { BaseModel, column } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

class Post extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public title: string
}

We start by defining 2 models that extends the BaseModel class and define columns on them. The model itself doesn't know how to fetch or persist the data and instead relies on an Adapter.

Useless without Adapter

The Adapter can run SQL queries to fetch the data or making an HTTP call to fetch data from some REST API endpoint. Which inturn means, the models are useless without adapters. That's why, this library is meant for developers creating ORM's and not users using ORM's.

Standardized API

However, the BaseModel class exposes a standardized API and does all the heavy lifting of normalizing the adapter results to model instances. For example:

import { BaseModel, column } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

const user = new User()
user.username = 'virk'

await user.save()

The user.save will ask the adapter to perform insert for the given model. A SQL adapter will perform an insert query, whereas a REST based adapter will perform a POST request to some endpoint.

Once the API call is over, the user.$persisted will return true.

Relationship example

Let's also see how to work with relationships.

import { BaseModel, column, belongsTo } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

class Post extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public title: string

  @belongsTo({ relatedModel: () => User })
  public author: User
}
  • First we define a belongsTo relationship on the post model.
  • Next we ask the adapter (depends on your implementation) to make an API call to some endpoint that returns an array of posts along with their authors.

Fetched JSON

[
  {
    "id": "1",
    "title": "AdonisJs 101",
    "author": {
      "id": 1,
      "username": "virk",
    }
  }
]

We can pass this data to the post model to create an array of model instances.

const posts = Post.$createMultipleFromAdapterResult(result)
posts.length  // 1
posts[0].title = 'Adonis 101'

// Mutate and update
posts[0].title = 'Adonis 102'
await posts[0].save()

Once the model is persisted, the save method will fire the update method on the adapter vs the insert.

Creating adapter

We make sure to keep the surface area small for the adapter by making it define handful of methods. However, you can extend the BaseModel to add additional methods that inturns invokes different methods on the adapter.

const restAdapter = {
  async insert (instance, attributes) {
    const response = await axios.post({
      url: instance.$getConstructor().url,
      body: attributes
    })
    instance.$consumeAdapterResult(response.data)
  },

  async delete (instance) {
    await axios.delete({
      url: `${instance.$getConstructor().url}/${instance.$getAttribute('id')}`,
    })
  },

  update (instance, dirtyAttributes) {
    const response = await axios.patch({
      url: `${instance.$getConstructor().url}/${instance.$getAttribute('id')}`,
      body: dirtyAttributes,
    })
    instance.$consumeAdapterResult(response.data)
  },

  find (model, key, value) {
    const result = await axios.get({
      url: instance.$getConstructor().url,
      params: {
        [key]: value,
      }
    })

    return model.$createFromAdapterResult(result.body[0])
  },

  findAll (model) {
    const result = await axios.get({
      url: instance.$getConstructor().url,
    })

    return model.$createMultipleFromAdapterResult(result.body)
  }
}

The find and findAll method gets the model constructor and not the instance, but they must return one or more instances of the model using $createFromAdapterResult and $createMultipleFromAdapterResult methods.

API

Please check the API docs generated via Typedoc

Maintainers

Harminder virk

About

Abstract data models with first class support for relationships

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published