Data models to manage data in OOP style with first class support for relationships
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.
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.
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.
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
.
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
.
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.
Please check the API docs generated via Typedoc