mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
9.6 KiB
314 lines
9.6 KiB
5 years ago
|
---
|
||
5 years ago
|
|
||
|
|
||
5 years ago
|
---
|
||
|
# Create and use models
|
||
5 years ago
|
{:.no_toc}
|
||
|
|
||
5 years ago
|
Radiks allows you to model your client data. You can then query this data and display it for a user in multi-player applications. A social application where users want to see the comments of other users is an example of a multi-player application. This page explains how to create a model in your distributed application using Radiks.
|
||
5 years ago
|
|
||
5 years ago
|
* TOC
|
||
|
{:toc}
|
||
|
|
||
5 years ago
|
## Overview of Model class extension
|
||
5 years ago
|
|
||
5 years ago
|
Blockstack provides a `Model` class you should extend to easily create, save, and fetch models. To create a model class, import the `Model` class from `radiks` into your application.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
|
import { Model, User } from 'radiks';
|
||
|
```
|
||
5 years ago
|
|
||
5 years ago
|
Then, create a class that extends this model, and provide a schema. Refer to <a href="https://github.com/blockstack/radiks/blob/master/src/model.ts" target="_blank">the <code>Model</code> class</a> in the `radiks` repo to get an overview of the class functionality.
|
||
5 years ago
|
|
||
5 years ago
|
Your new class must define a static `className` property. This property is used when storing and querying information. If you fail to add a `className`, Radiks defaults to the actual model's class name (`foobar.ts`) and your application will behave unpredictably.
|
||
5 years ago
|
|
||
5 years ago
|
The example class code extends `Model` to create a class named `Todo`:
|
||
5 years ago
|
|
||
|
```javascript
|
||
|
import { Model, User } from 'radiks';
|
||
|
|
||
|
class Todo extends Model {
|
||
|
static className = 'Todo';
|
||
|
static schema = { // all fields are encrypted by default
|
||
|
title: String,
|
||
|
completed: Boolean,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// after authentication:
|
||
|
const todo = new Todo({ title: 'Use Radiks in an app' });
|
||
|
await todo.save();
|
||
|
todo.update({
|
||
|
completed: true,
|
||
|
});
|
||
|
await todo.save();
|
||
|
|
||
|
const incompleteTodos = await Todo.fetchOwnList({ // fetch todos that this user created
|
||
|
completed: false
|
||
|
});
|
||
|
console.log(incompleteTodos.length); // 0
|
||
|
```
|
||
|
|
||
5 years ago
|
## How to create your own Model
|
||
5 years ago
|
|
||
5 years ago
|
The following sections guide you through the steps in defining your own class.
|
||
|
|
||
|
### Define a class schema
|
||
|
|
||
|
Every class must have a static `schema` property which defines the attributes of a model using field/value pairs, for example:
|
||
|
|
||
|
```javascript
|
||
|
class Todo extends Model {
|
||
|
static className = 'Todo';
|
||
|
static schema = { // all fields are encrypted by default
|
||
|
title: String,
|
||
|
completed: Boolean,
|
||
|
}
|
||
|
};
|
||
|
```
|
||
5 years ago
|
|
||
5 years ago
|
The `key` in this object is the field name and the value, for example, `String`, `Boolean`, or `Number`. In this case, the `title` is a `String` field. Alternatively, you can pass options instead of a type.
|
||
5 years ago
|
|
||
5 years ago
|
To define options, pass an object, with a mandatory `type` field. The only supported option right now is `decrypted`. This defaults to `false`, meaning the field is encrypted before the data is stored publicly. If you specify `true`, then the field is not encrypted.
|
||
5 years ago
|
|
||
5 years ago
|
Storing unencrypted fields is useful if you want to be able to query the field when fetching data. A good use-case for storing decrypted fields is to store a `foreignId` that references a different model, for a "belongs-to" type of relationship.
|
||
5 years ago
|
|
||
5 years ago
|
**Never add the `decrypted` option to fields that contain sensitive user data.** Blockstack data is stored in a decentralized Gaia storage and anyone can read the user's data. That's why encrypting it is so important. If you want to filter sensitive data, then you should do it on the client-side, after decrypting it.
|
||
5 years ago
|
|
||
5 years ago
|
### Include defaults
|
||
5 years ago
|
|
||
5 years ago
|
You may want to include an optional `defaults` static property for some field values. For example, in the class below, the `likesDogs` field is a `Boolean`, and the default is `true`.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
import { Model } from 'radiks';
|
||
|
|
||
|
class Person extends Model {
|
||
|
static className = 'Person';
|
||
|
|
||
|
static schema = {
|
||
|
name: String,
|
||
|
age: Number,
|
||
|
isHuman: Boolean,
|
||
|
likesDogs: {
|
||
|
type: Boolean,
|
||
|
decrypted: true // all users will know if this record likes dogs!
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static defaults = {
|
||
|
likesDogs: true
|
||
|
}
|
||
|
}
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
If you wanted to add a default for `isHuman`, you would simply add it to the `defaults` as well. Separate each field with a comma.
|
||
5 years ago
|
|
||
5 years ago
|
### Extend the User model
|
||
5 years ago
|
|
||
5 years ago
|
Radiks also supplies <a href="https://github.com/blockstack/radiks/blob/master/src/models/user.ts" target="_blank">a default <code>User</code> model</a>. You can also extend this model to add your own attributes.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
|
import { User } from 'radiks';
|
||
|
|
||
|
// For example I want to add a public name on my user model
|
||
|
class MyAppUserModel extends User {
|
||
|
static schema = {
|
||
|
...User.schema,
|
||
|
displayName: {
|
||
|
type: String,
|
||
|
decrypted: true,
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
```
|
||
|
|
||
5 years ago
|
The default `User` model defines a `username`, but you can add a `displayName` to allow the user to set unique name in your app.
|
||
5 years ago
|
|
||
|
## Use a model you have defined
|
||
|
|
||
|
In this section, you learn how to use a model you have defined.
|
||
|
|
||
|
### About the _id attribute
|
||
|
|
||
5 years ago
|
All model instances have an `_id` attribute. An `_id` is used as a primary key when storing data and is used for fetching a model. Radiks also creates a `createdAt` and `updatedAt` property when creating and saving models.
|
||
5 years ago
|
|
||
5 years ago
|
If, when constructing a model's instance, you don't pass an `_id`, Radiks creates an `_id` for you automatically. This automatically created id uses the [`uuid/v4`](https://github.com/kelektiv/node-uuid) format. This automatic `_id` is returned by the constructor.
|
||
5 years ago
|
|
||
|
|
||
|
### Construct a model instance
|
||
5 years ago
|
|
||
5 years ago
|
To create an instance, pass some attributes to the constructor of that class:
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
const person = new Person({
|
||
|
name: 'Hank',
|
||
|
isHuman: false,
|
||
|
likesDogs: false // just an example, I love dogs!
|
||
|
})
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
### Fetch an instance
|
||
5 years ago
|
|
||
5 years ago
|
To fetch an existing instance of an instance, you need the instance's `id` property. Then, call the `findById()` method or the `fetch()` method, which returns a promise.
|
||
5 years ago
|
|
||
|
```javascript
|
||
5 years ago
|
const person = await Person.findById('404eab3a-6ddc-4ba6-afe8-1c3fff464d44');
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
|
|
||
5 years ago
|
After calling these methods, Radiks automatically decrypts all encrypted fields.
|
||
5 years ago
|
|
||
5 years ago
|
### Access attributes
|
||
|
|
||
5 years ago
|
Other than `id`, all attributes are stored in an `attrs` property on the instance.
|
||
5 years ago
|
|
||
|
```javascript
|
||
5 years ago
|
const { name, likesDogs } = person.attrs;
|
||
|
console.log(`Does ${name} like dogs?`, likesDogs);
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
### Update attributes
|
||
5 years ago
|
|
||
5 years ago
|
To quickly update multiple attributes of an instance, pass those attributes to the `update` method.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
const newAttributes = {
|
||
|
likesDogs: false,
|
||
|
age: 30
|
||
|
}
|
||
|
person.update(newAttributes)
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
Important, calling `update` does **not** save the instance.
|
||
5 years ago
|
|
||
5 years ago
|
### Save changes
|
||
5 years ago
|
|
||
5 years ago
|
To save an instance to Gaia and MongoDB, call the `save()` method, which returns a promise. This method encrypts all attributes that do not have the `decrypted` option in their schema. Then, it saves a JSON representation of the model in Gaia, as well as in the MongoDB.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
await person.save();
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
### Delete an instance
|
||
5 years ago
|
|
||
5 years ago
|
To delete an instance, just call the `destroy` method on it.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
await person.destroy();
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
## Query a model
|
||
5 years ago
|
|
||
5 years ago
|
To fetch multiple records that match a certain query, use the class's `fetchList()` function. This method creates an HTTP query to Radiks-server, which then queries the underlying database. Radiks-server uses the `query-to-mongo` package to turn an HTTP query into a MongoDB query.
|
||
5 years ago
|
|
||
|
Here are some examples:
|
||
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
const dogHaters = await Person.fetchList({ likesDogs: false });
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
|
Or, imagine a `Task` model with a `name`, a boolean for `completed`, and an `order` attribute.
|
||
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
class Task extends Model {
|
||
|
static className = 'Task';
|
||
|
|
||
|
static schema = {
|
||
|
name: String,
|
||
|
completed: {
|
||
|
type: Boolean,
|
||
|
decrypted: true,
|
||
|
},
|
||
|
order: {
|
||
|
type: Number,
|
||
|
decrypted: true,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const tasks = await Task.fetchList({
|
||
|
completed: false,
|
||
|
sort: '-order'
|
||
|
})
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
You can read the [`query-to-mongo`](https://github.com/pbatey/query-to-mongo) package documentation to learn how to do complex querying, sorting, limiting, and so forth.
|
||
5 years ago
|
|
||
5 years ago
|
## Count models
|
||
|
|
||
|
You can also get a model's `count` record directly.
|
||
|
|
||
|
```javascript
|
||
5 years ago
|
const dogHaters = await Person.count({ likesDogs: false });
|
||
|
// dogHaters is the count number
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
## Fetch models created by the current user
|
||
5 years ago
|
|
||
5 years ago
|
Use the `fetchOwnList` method to find instances that were created by the current user. By using this method, you can preserve privacy, because Radiks uses a `signingKey` that only the current user knows.
|
||
5 years ago
|
|
||
|
```javascript
|
||
|
const tasks = await Task.fetchOwnList({
|
||
|
completed: false
|
||
|
});
|
||
|
```
|
||
|
|
||
5 years ago
|
## Manage relational data
|
||
5 years ago
|
|
||
|
It is common for applications to have multiple different models, where some reference another. For example, imagine a task-tracking application where a user has multiple projects, and each project has multiple tasks. Here's what those models might look like:
|
||
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
class Project extends Model {
|
||
|
static className = 'Project';
|
||
|
static schema = { name: String }
|
||
|
}
|
||
|
|
||
|
class Task extends Model {
|
||
|
static className = 'Task';
|
||
|
static schema = {
|
||
|
name: String,
|
||
|
projectId: {
|
||
|
type: String,
|
||
|
decrypted: true,
|
||
|
}
|
||
|
completed: Boolean
|
||
|
}
|
||
|
}
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
Whenever you save a task, you should save a reference to the project it's in:
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
const task = new Task({
|
||
|
name: 'Improve radiks documentation',
|
||
|
projectId: project._id
|
||
|
})
|
||
|
await task.save();
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
|
Then, later you'll want to fetch all tasks for a certain project:
|
||
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
const tasks = await Task.fetchList({
|
||
|
projectId: project._id,
|
||
|
})
|
||
5 years ago
|
```
|
||
5 years ago
|
|
||
5 years ago
|
Radiks lets you define an `afterFetch` method. Use this method to automatically fetch child records when you fetch the parent instance.
|
||
5 years ago
|
|
||
5 years ago
|
```javascript
|
||
5 years ago
|
class Project extends Model {
|
||
|
static className = 'Project';
|
||
|
static schema = { name: String }
|
||
|
|
||
|
async afterFetch() {
|
||
|
this.tasks = await Task.fetchList({
|
||
|
projectId: this.id,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const project = await Project.findById('some-id-here');
|
||
|
console.log(project.tasks); // will already have fetched and decrypted all related tasks
|
||
5 years ago
|
```
|