How to automigrate in Loopback
Asked Answered
G

4

10

I have renamed a number of models and tables in my loopback application, however I must now migrate to this model definition.

I need to run autoMigrate(). It must be run on a dataSource object but the documentation provides no help regarding acquiring one of these.

so far I have created a new script in /boot containing:

var loopback = require('loopback');
var app = module.exports = loopback();
app.loopback.DataSource.automigrate()

but this data source object does not contain an autoMigrate function...

I have tried running strongloop arc to use the auto migrate button present there, but the page crashes with this error:

Uncaught Error: [$injector:modulerr] Failed to instantiate module Arc due to:
Error: [$injector:modulerr] Failed to instantiate module Metrics due to:
Error: [$injector:nomod] Module 'Metrics' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.3.20/$injector/nomod?p0=Metrics
    at http://localhost:56073/scripts/vendor/angular/angular.js:63:12
    at http://localhost:56073/scripts/vendor/angular/angular.js:1778:17
    at ensure (http://localhost:56073/scripts/vendor/angular/angular.js:1702:38)
    at module (http://localhost:56073/scripts/vendor/angular/angular.js:1776:14)
    at http://localhost:56073/scripts/vendor/angular/angular.js:4131:22
    at forEach (http://localhost:56073/scripts/vendor/angular/angular.js:326:20)
    at loadModules (http://localhost:56073/scripts/vendor/angular/angular.js:4115:5)
    at http://localhost:56073/scripts/vendor/angular/angular.js:4132:40
    at forEach (http://localhost:56073/scripts/vendor/angular/angular.js:326:20)
    at loadModules (http://localhost:56073/scripts/vendor/angular/angular.js:4115:5)
http://errors.angularjs.org/1.3.20/$injector/modulerr?p0=Metrics&p1=Error%3…F%2Flocalhost%3A56073%2Fscripts%2Fvendor%2Fangular%2Fangular.js%3A4115%3A5)
    at http://localhost:56073/scripts/vendor/angular/angular.js:63:12
    at http://localhost:56073/scripts/vendor/angular/angular.js:4154:15
    at forEach (http://localhost:56073/scripts/vendor/angular/angular.js:326:20)
    at loadModules (http://localhost:56073/scripts/vendor/angular/angular.js:4115:5)
    at http://localhost:56073/scripts/vendor/angular/angular.js:4132:40
    at forEach (http://localhost:56073/scripts/vendor/angular/angular.js:326:20)
    at loadModules (http://localhost:56073/scripts/vendor/angular/angular.js:4115:5)
    at createInjector (http://localhost:56073/scripts/vendor/angular/angular.js:4041:11)
    at doBootstrap (http://localhost:56073/scripts/vendor/angular/angular.js:1455:20)
    at bootstrap (http://localhost:56073/scripts/vendor/angular/angular.js:1476:12)
http://errors.angularjs.org/1.3.20/$injector/modulerr?p0=Arc&p1=Error%3A%20…%2Flocalhost%3A56073%2Fscripts%2Fvendor%2Fangular%2Fangular.js%3A1476%3A12)

I just need to update the model, and do not understand why this is so difficult. Does anybody know how to overcome these obstacles? thanks!

Galah answered 13/10, 2016 at 20:10 Comment(0)
S
16

Two problems.

First, you are showing an Angular client-side error, while your problem is with the server side.

Now, back to the core of the problem.

A boot script must export a single function, that will accept either just the loopback object (your server) in the case of synchronous boot scripts, or the loopback object and a callback (for asynchronous boot scripts).

Writing boot scripts is documented here

The function datasource.automigrate can be called synchronously. If you call it without parameters, by default it will execute for all models in your app, which is most likely what you want.

So, if the database you defined has for name mysql (defined in ./server/datasources.json) the boot script is :

module.exports = function (app) {
   app.dataSources.mysql.automigrate();
   console.log("Performed automigration.");
}

You can check that this works by running node . and NOT by going in your browser at the adress the server is listening. (Your Angular-related error has nothing to do with the boot script, it is related to client-side files, most likely located in the client folder).

It should display in the terminal

Performed automigration.
Strophanthin answered 14/10, 2016 at 10:8 Comment(0)
N
2

I really liked how Jesé Rodríguez's answer runs autoupdate on each model one at a time so I can know which ones were updated. Unfortunately it iterates over app.model which is incorrect and can have some negative side effects.

Here's my version (requires Node 8+ for async/await):

'use strict';

// Update (or create) database schema (https://loopback.io/doc/en/lb3/Creating-a-database-schema-from-models.html)
// This is effectively a no-op for the memory connector
module.exports = function(app, cb) {
  updateDatabaseSchema(app).then(() => {
    process.nextTick(cb);
  });
};

async function updateDatabaseSchema(app) {
  const dataSource = app.dataSources.db;

  for (let model of app.models()) {
    if (await doesModelNeedUpdate(dataSource, model.modelName) === true) {
      await updateSchemaForModel(dataSource, model.modelName);
    }
  }
}

function doesModelNeedUpdate(dataSource, model) {
  return new Promise((resolve, reject) => {
    dataSource.isActual(model, (err, actual) => {
      if (err) reject(err);
      resolve(!actual);
    });
  });
}

function updateSchemaForModel(dataSource, model) {
  return new Promise((resolve, reject) => {
    dataSource.autoupdate(model, (err, result) => {
      if (err) reject(err);
      console.log(`Autoupdate performed for model ${model}`);
      resolve();
    });
  });
}
Negation answered 7/11, 2018 at 21:13 Comment(0)
H
1

You need to use app.dataSources.<dataSourceName> instead of app.loopback.DataSource inside your boot script.

Try this in your boot script:

module.exports = function (app) {
'use strict'
var mysql = app.dataSources.mysql;

console.log('-- Models found:', Object.keys(app.models));

for (var model in app.models) {
    console.log("Cheking if table for model " + model + " is created and up-to-date in DB...");
    mysql.isActual(model, function (err, actual) {
        if (actual) {
            console.log("Model " + model + " is up-to-date. No auto-migrated.");
        } else {
            console.log('Difference found! Auto-migrating model ' + model + '...');
            mysql.autoupdate(model, function () {
                console.log("Auto-migrated model " + model + " successfully.");
            });
        }
    });
} };

You can see a basic example I have on GitHub for this: https://github.com/jeserodz/loopback-models-automigration-example

Hoofed answered 13/10, 2016 at 23:6 Comment(8)
Thanks for the response, however running it gave me:Galah
Thanks for the response, however running it gave me TypeError: Cannot read property 'settings' of undefinedGalah
var mysql = app.dataSources.mysql; should be changed to the name of your datasource. The same used on the server/model-config.json for your models.Knacker
yes I replaced all mentions of mysql with postgresql in my version, that produced the error. the error pointed to the .isActual line, perhaps the postgres connected does not support this functionGalah
This is way, way too complicated for performing an automigrate. See my answer, you don't need to pass a list of models to automigrate, by default it will run for all. I'm not downvoting since you're new hereStrophanthin
@Galah Apparently this is caused from iterating over the models incorrectly: github.com/strongloop/loopback/issues/…Negation
@JD you dont have to give the name of the engine (mysql, postgresql, etc), but the id of the datasource as defined in your datasources.jsonLentic
@JeséRodríguez You declare "model" using var, which means that the asynchronous callbacks will not always get the correct value. Remember callbacks are not guaranteed to be executed sequentially, so model can actually be any value of the iterator, even duplicated ones, specially on fast machines. Declare "model" with let to fix it.Lentic
M
0

Create a new file(automigrate.js) in the server folder in bin directory and paste the following code in that file. Here I am using cassandra as a datasource and inserting dummy data in the table. You can use this code for different datasources. Change the table schema according to your own requirenments.

var path = require('path');
var app = require(path.resolve(__dirname, '../server/server'));
var ds = app.datasources.cassandra;
ds.automigrate('dataSourceTesting', function(err) {
  if (err) throw err;

  var accounts = [
    {
      name: '[email protected]',
      address: "asdasd"
    },
    {
      name: '[email protected]',
      address: "asdasdasd"
    },
  ];
  var count = accounts.length;
  accounts.forEach(function(account) {
    app.models.dataSourceTesting.create(account, function(err, model) {
      if (err) throw err;

      console.log('Created:', model);

      count--;
      if (count === 0)
        ds.disconnect();
    });
  });
});

Code Source: loopback github repository.

Mental answered 17/7, 2017 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.