2016-04-04 21:51:00 +02:00

14 KiB
Executable File

SQL ORM

The SQL ORM aims to provide a simple library for common tasks when querying databases in JavaScript, and forming relations between these objects, taking a lot of ideas from the the Data Mapper Pattern.

The Strapi SQL ORM doesn't force you to use any specific validation scheme, provides flexible and efficient relation/nested-relation loading, and first class transaction support.

It's a lean Object Relational Mapper, allowing you to drop down to the raw interface whenever you need a custom query that doesn't quite fit with the stock conventions.

Promises

The Strapi SQL ORM uses its own copy of the "bluebird" promise library. That means methods are chainables and return promises.

User.forge()
  .where('reviews', '>', 100)
  .limit(10)
  .offset(30)
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

Query language basics

Fetch a record

Example:

User.forge({id: 123})
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20
}

It is also possible to eager loading any specified relations named on the model:

User.forge({id: 123})
  .fetch({
    withRelated: ['friends', 'likes']
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20,
  "friends": [{
    "id": 99,
    "firstname": "Peter",
    "lastname": "Arrow",
    "age": 53
  }, {
    "id": 48,
    "firstname": "Andrea",
    "lastname": "Nelson",
    "age": 32
  }],
  "likes": [{
    "post_id": 1829,
    "date": "2016-04-22T06:00:00Z"
  }, {
    "post_id": 7849,
    "date": "2016-03-22T07:32:45Z"
  }]
}

Fetch records

Example:

User.forge()
  .fetchAll()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

[{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20
}, {
   "id": 99,
  "firstname": "Peter",
  "lastname": "Arrow",
  "age": 53
}, {
  ...
}]

It is also possible to eager loading any specified relations named on the model:

User.forge()
  .fetchAll({
    withRelated: ['friends', 'likes']
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

Create a record

Example:

User.forge({
    'firstname': 'John',
    'lastname': 'Doe',
    'age': 20
  })
  .save()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20
}

Create a record with one-to-one/one-to-many relationship

The only difference between the two relationship cases will be on the database constraints layer:

  • one-to-one: it will allow only one user with the sponsor ID 849
  • one-to-many: it will allow more than one user with the sponsor ID 849

Example:

User.forge({
    'firstname': 'John',
    'lastname': 'Doe',
    'age': 20,
    'sponsor': 849
  })
  .save()
  .then(function (model) {
    return model.fetch({
      withRelated: 'sponsor'
    });
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20,
  "sponsor": {
    "id": 849,
    "firstname": "Peter",
    "lastname": "Arrow",
    "age": 53
  }
}

Create a record with many-to-many relationship

const friend1 = User.forge({id: 1});
const friend2 = User.forge({id: 2});

User.forge({
    'firstname': 'John',
    'lastname': 'Doe',
    'age': 20
  })
  .save()
  .tap(function (model) {
    return model.friends().attach([friend1, friend2]);
  })
  .then(function (model) {
    return model.fetch({
      withRelated: 'friends'
    });
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20,
  "friends": [{
    "id": 1,
    "firstname": "Peter",
    "lastname": "Arrow",
    "age": 53
  }, {
    "id": 2,
    "firstname": "Andrea",
    "lastname": "Nelson",
    "age": 32
  }]
}

Update a record

User.forge({
    id: 123
  })
  .save({
    'lastname': 'Does',
    'age': 40
  }, {path: true})
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function(err) {
    reject(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Does",
  "age": 40,
}

Update a record with a one-to-one/one-to-many relationship

This is exactly the same as update a record without relationship.

User.forge({
    id: 123
  })
  .save({
    'sponsor': 756
  }, {path: true})
  .then(function (model) {
    return model.fetch({
      withRelated: 'sponsor'
    });
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function(err) {
    reject(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Does",
  "age": 40,
  "sponsor": {
    "id": 756,
    "firstname": "Martin",
    "lastname": "Terry",
    "age": 37
  }
}

Update a record with a many-to-many relationship

This is also exactly the same as update a record without relationship. However, there are two cases:

  • Add new relationship(s) to a record
  • Remove current(s) relationship(s) to the record

Add new relationship(s) to a record

const friend3 = User.forge({id: 3});

User.forge({
    id: 123
  })
  .friends()
  .attach([friend3])
  .then(function (model) {
    return model.fetch({
      withRelated: 'friends'
    });
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20,
  "friends": [{
    "id": 1,
    "firstname": "Peter",
    "lastname": "Arrow",
    "age": 53
  }, {
    "id": 2,
    "firstname": "Andrea",
    "lastname": "Nelson",
    "age": 32
  }, {
    "id": 3,
    "firstname": "Paul",
    "lastname": "Thomas",
    "age": 78
  }]
}

Remove current(s) relationship(s) to a record

const friend2 = User.forge({id: 2});
const friend3 = User.forge({id: 3});

User.forge({
    id: 123
  })
  .friends()
  .detach([friend2, friend3])
  .then(function (model) {
    return model.fetch({
      withRelated: 'friends'
    });
  })
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

The console.log() will print out:

{
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "age": 20,
  "friends": [{
    "id": 1,
    "firstname": "Peter",
    "lastname": "Arrow",
    "age": 53
  }]
}

Delete a record

User.forge({
    id: 123
  })
  .destroy()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function(err) {
    console.log(err);
  });

The console.log() will print out:

null

Query options

raw

User.forge({
    id: 123
  })
  .query(function (qb) {
    qb.offset(0).limit(10);
  }))
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  })

count

Count the entries for a query:

User.forge()
  .where({
    'age': 20
  })
  .count()
  .then(function (count) {
    console.log(count);
  })
  .catch(function (err) {
    console.log(err);
  })

where

Adds a where clause to the query:

User.forge()
  .where({
    'firstname': 'John',
    'age': 20
  })
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

orderBy

Adds an orderBy clause to the query.

Using the object syntax:

User.forge()
  .query({
    'orderBy': 'firstname'
  })
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

This syntax doesn't allow to update ASC / DESC parameter. To do so, you have to use the function syntax below:

User.forge()
  .query(function (qb) {
    qb.orderBy('firstname', 'desc');
  }))
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

groupBy

Adds an groupBy clause to the query:

User.forge()
  .query({
    'groupBy': 'age'
  })
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

limit

Adds an limit clause to the query:

User.forge()
  .query({
    'limit': 20
  })
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

offset

Adds an offset clause to the query:

User.forge()
  .query({
    'offset': 10
  })
  .fetch()
  .then(function (model) {
    console.log(model.toJSON());
  })
  .catch(function (err) {
    console.log(err);
  });

Lifecycle events

Lifecycle callbacks are functions you can define to run at certain times in a query. They are hooks that you can tap into in order to change data. Strapi exposes a handful of lifecycle callbacks by default.

Lifecycle events must be placed in your JavaScript file of your models.

Common callbacks

beforeSave

Fired before an insert or update query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save.

Code:

beforeSave: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes that will be inserted or updated.
  • options: Options object passed to save.

Returns: Promise.

afterSave

Fired after an insert or update query.

Code:

afterSave: (model, response, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • response: The database response.
  • options: Options object passed to save.

Returns: Promise.

Callbacks on fetch

beforeFetch

Fired before a fetch operation. A promise may be returned from the event handler for async behavior.

Code:

beforeFetch: (model, columns, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • columns: The columns to be retrieved by the query.
  • options: Options object passed to fetch.

Returns: Promise.

afterFetch

Fired after a fetch operation. A promise may be returned from the event handler for async behavior.

Code:

afterFetch: (model, response, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • response: SQL query response.
  • options: Options object passed to fetch.

Returns: Promise. If the handler returns a promise, fetch will wait for it to be resolved.

Callbacks on create

beforeCreate

Fired before insert query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.

Code:

beforeCreate: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes that will be inserted.
  • options: Options object passed to save.

Returns: Promise.

afterCreate

Fired after an insert query.

Code:

afterCreate: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes inserted.
  • options: Options object passed to save.

Returns: Promise.

Callbacks on update

beforeUpdate

Fired before an update query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will cancel the save operation.

Code:

beforeUpdate: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes that will be updated.
  • options: Options object passed to save.

Returns: Promise.

afterUpdate

Fired after an update query.

Code:

afterUpdate: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes updated.
  • options: Options object passed to save.

Returns: Promise.

Callbacks on destroy

beforeDestroy

Fired before a delete query. A promise may be returned from the event handler for async behavior. Throwing an exception from the handler will reject the promise and cancel the deletion.

Code:

beforeDestroy: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes that will be destroyed.
  • options: Options object passed to save.

Returns: Promise.

afterDestroy

Fired before a delete query. A promise may be returned from the event handler for async behavior.

Code:

afterDestroy: (model, attrs, options) => {
  return new Promise();
},

Parameters:

  • model: The model firing the event.
  • attrs: Attributes destroyed.
  • options: Options object passed to save.

Returns: Promise.