Как организовать приложение узла, которое использует sequelize?

125

Я ищу пример приложения nodejs, которое использует sequelize ORM.

Меня больше всего беспокоит то, что практически невозможно определить ваши модели в отдельных файлах js, если эти модели имеют сложные отношения друг с другом из-за циклов зависимостей require (). Может быть, люди определяют все свои модели в одном очень-очень длинном файле?

Меня в основном интересует, как модели определяются и используются в приложении. Я хотел бы получить некоторое подтверждение того, что то, что я делаю сам по себе, - это «хороший» способ делать что-то.

mkoryak
источник
2
Я добавил пример, который может кому-то помочь github.com/shaishab/sequelize-express-example
Шайшаб Рой
Я написал статью о наших решениях: medium.com/@ismayilkhayredinov/...
hypeJunction

Ответы:

125

Краткий рассказ

Хитрость в этом случае состоит не в том, чтобы инициализировать модель в файле, а просто предоставить необходимую информацию для ее инициализации и позволить централизованному модулю позаботиться о настройке и создании экземпляров модели.

Итак, шаги следующие:

  • Имейте несколько файлов модели с данными о модели, такими как поля, отношения и параметры.
  • Имейте одноэлементный модуль, который загружает все эти файлы и настраивает все классы и отношения модели.
  • Настройте модуль singleton в файле app.js.
  • Получите классы модели из одноэлементного модуля , не используйте их requireв ваших файлах моделей, вместо этого загрузите модели из одноэлементного модуля .

Более длинная история

Вот более подробное описание этого решения с соответствующим исходным кодом:

http://jeydotc.github.io/blog/2012/10/30/EXPRESS-WITH-SEQUELIZE.html

РЕДАКТИРОВАТЬ: это очень старый ответ! (читайте информацию)

Он старый и во многом ограниченный!

  • Во-первых , как @jinglesthula упомянул в комментариях (и я тоже это испытал), есть проблемы с требованием этих файлов. Это потому, requireчто не работает так же, как readdirSync!

  • Во-вторых, вы очень ограничены в отношениях - код не предусматривает параметры для этих ассоциаций, поэтому вы НЕ МОЖЕТЕ создать, belongsToManyпоскольку для этого требуется throughсвойство. Можно сделать самые простые соц.

  • В-третьих, вы очень ограничены в модельных отношениях! Если вы внимательно прочитаете код, вы увидите, что отношения - это объект а не массив , поэтому, если вы хотите создать более одной ассоциации одного типа (например, два раза belongsTo) - вы не можете!

  • В-четвертых - вам не нужна эта синглтон-штука. Каждый модуль в nodejs сам по себе одноэлементный, поэтому все это делает довольно сложным без причины.

Вы должны увидеть ответ Фермы! (Ссылка на статью не работает, но я исправлю ее с помощью этого официального образца из sequelize: https://github.com/sequelize/express-example/blob/master/models/index.js - вы можете просмотреть весь проект, чтобы понять, что происходит).

ps Я редактирую этот пост, так как за него проголосовали так, что люди даже не увидят новых ответов (как и я).

Изменить: просто изменил ссылку на копию того же сообщения, но на странице Github

user1778770
источник
Кроме того, у меня создалось впечатление, что все requireмодули d в node были в некотором смысле одиночными, потому что код в них выполняется один раз, а затем кэшируется, так что в следующий раз, когда они вам потребуются, вы получите ссылку на кешированный объект. Разве это не вся картина?
mkoryak 01
1
@mkoryak, вы правы - все модули commonjs в node фактически являются одиночными, поскольку возвращаемое значение кешируется после первого выполнения. nodejs.org/api/modules.html#modules_caching
Кейси Флинн,
2
Итак, пример можно упростить, удалив сложную часть синглтона и просто поместив module.exports = new OrmClass (). Я попробую, спасибо за отзыв :)
user1778770
2
На всякий случай, если у кого-то болит голова, я спасу тебя. У меня были проблемы с кодом, указанным в статье на github, в которой основное внимание уделялось путям. Пришлось добавить. к требованию (например: var object = require ('.' + modelsPath + "/" + name);), а также поместите return if name.indexOf ('DS_Store')> -1 в forEach в функции init (ура OSX). Надеюсь, это поможет.
jinglesthula
как упоминалось в @jinglesthula - в образце есть некоторые изменения / ошибки для загрузки файлов в каталог (особенно если он вложен где-то еще). Я бы также добавил возможность передавать параметры отношениям, поскольку они очень важны (например, имя внешнего ключа, если ему разрешено быть нулевым и т. Д.)
Андрей Попов
96

На сайте SequelizeJS есть статья, которая решает эту проблему.

Ссылка не работает, но вы можете найти рабочий пример проекта здесь и просматривать его. См. Отредактированный ответ выше, чтобы понять, почему это лучшее решение.

Выдержка из статьи:

  • Модели / index.js

    Идея этого файла - настроить соединение с базой данных и собрать все определения модели. Как только все будет готово, мы вызовем метод, связанный с каждой из моделей. Этот метод можно использовать для связывания модели с другими.

          var fs        = require('fs')
            , path      = require('path')
            , Sequelize = require('sequelize')
            , lodash    = require('lodash')
            , sequelize = new Sequelize('sequelize_test', 'root', null)
            , db        = {} 
    
          fs.readdirSync(__dirname)
            .filter(function(file) {
              return (file.indexOf('.') !== 0) && (file !== 'index.js')
            })
            .forEach(function(file) {
              var model = sequelize.import(path.join(__dirname, file))
              db[model.name] = model
            })
    
          Object.keys(db).forEach(function(modelName) {
            if (db[modelName].options.hasOwnProperty('associate')) {
              db[modelName].options.associate(db)
            }
          })
    
          module.exports = lodash.extend({
            sequelize: sequelize,
            Sequelize: Sequelize
          }, db)
ферма
источник
12
Именно так рекомендует это делать Sequelize. Я бы принял это как правильный ответ.
jpotts18
3
Это хорошо, но вы не можете использовать модель из методов экземпляра другой модели, или, возможно, я что-то пропустил.
mlkmt
1
Страница больше не существует
Майк Чил
1
Вот рабочая ссылка: sequelize.readthedocs.org/en/1.7.0/articles/express
chrisg86
3
@mlkmt можно! Поскольку у вас есть доступ к sequelizeпеременной в файле модели, вы можете получить доступ к другой модели с помощью sequelize.models.modelName.
Guilherme Sehn
29

Я создал пакет sequelize-connect, чтобы помочь людям справиться с этой проблемой. Это соответствует предложенному здесь соглашению Sequelize: http://sequelize.readthedocs.org/en/1.7.0/articles/express/

Кроме того, по своему интерфейсу он немного больше похож на Mongoose. Он позволяет вам указать набор местоположений, в которых находятся ваши модели, а также позволяет вам определять настраиваемую функцию сопоставления для соответствия вашим файлам моделей.

Использование в основном такое:

var orm = require('sequelize-connect');

orm.discover = ["/my/model/path/1", "/path/to/models/2"];      // 1 to n paths can be specified here
orm.connect(db, user, passwd, options);                        // initialize the sequelize connection and models

Затем вы можете получить доступ к моделям и продолжить так:

var orm       = require('sequelize-connect');
var sequelize = orm.sequelize;
var Sequelize = orm.Sequelize;
var models    = orm.models;
var User      = models.User;

Надеюсь, это кому-то поможет.

jspizziri
источник
3
Ссылка на статью немного помогает. Лучше цитировать некоторые документы. Показывать фрагмент кода - это здорово .... Но на самом деле создание библиотеки, которая решает проблему, и размещение ее в NPM - это фантастика и заслуживает большего внимания! +1 и отметим ваш проект.
Stijn de Witt
9

Я начал использовать Sequelize в приложении Express.js. Вскоре вы столкнулись с проблемами описываемого вами характера. Возможно, я не совсем понимал Sequelize, но для меня делать что-то большее, чем просто выбор из одной таблицы, было не очень удобно. И там, где обычно вы использовали бы выбор из двух или более таблиц или объединение в чистом SQL, вам пришлось бы запускать отдельные запросы, а с асинхронной природой Node это просто добавляло сложности.

Поэтому я отказался от использования Sequelize. Кроме того, я перехожу с использования ЛЮБОЙ выборки данных из БД в моделях. На мой взгляд, лучше полностью абстрагироваться от получения данных. И причины таковы: представьте, что вы не просто используете MySQL (в моем случае я использую MySQL и MongoDB одновременно), но вы можете получать свои данные от любого поставщика данных и любого транспортного метода, например SQL, no-SQL, файловая система, внешний API, FTP, SSH и т. д. Если вы попытаетесь сделать все это в моделях, вы в конечном итоге создадите сложный и трудный для понимания код, который будет трудно обновлять и отлаживать.

Теперь то , что вы хотите сделать , это есть модели получают данные из слоя , который знает , где и как его получить, но ваши модели только использовать методы API, например fetch, save,delete т. Д. И внутри этого уровня у вас есть определенные реализации для определенных поставщиков данных. Например, вы можете запросить определенные данные из файла PHP на локальном компьютере или из API Facebook, или из Amazon AWS, или из удаленного HTML-документа и т. Д.

PS некоторые из этих идей были позаимствованы у Architect by Cloud9 : http://events.yandex.ru/talks/300/

mvbl fst
источник
Это действительные пункты, но я предпочел бы избежать реализовав fetch, save, и deleteт.д. вне Sequelizeучитывая , что структура уже предоставляет средства. Это лучше, но менее удобно иметь отдельный слой выборки. В то же время, вероятно , можно добавить выборки слой абстракции вокруг Sequelize , но затем раствор более сложным, для спорного победы.
Зорайр
этот учебник будет очень полезен: продолжение + экспресс-пример
Лукас До Амарал
@ mvbl-fst Вы только что описали слой DAO. Допустим, у вас есть несколько пользователей в базе данных SQL и разные пользователи в файловой системе. У вас должны быть два DAO, которые абстрагируются, как получить каждый из них, затем бизнес-уровень, который объединяет пользователей вместе (возможно, даже адаптирует некоторые свойства) и передает их обратно на ваш маршрут (уровень представления).
DJDaveMark
5

Я настроил его как Farm и описал в документации.

Но у меня была дополнительная проблема, заключающаяся в том, что в моих методах экземпляра и методах класса, которые я присоединял к моделям в каждой функции, мне нужно было бы потребовать индексный файл для удержания других объектов базы данных.

Решил, сделав их доступными для всех моделей.

var Config = require('../config/config');

 var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var _ = require('lodash');
var sequelize;
var db = {};

var dbName, dbUsername, dbPassword, dbPort, dbHost;
// set above vars

var sequelize = new Sequelize(dbName, dbUsername, dbPassword, {
dialect: 'postgres', protocol: 'postgres', port: dbPort, logging: false, host: dbHost,
  define: {
    classMethods: {
        db: function () {
                    return db;
        },
        Sequelize: function () {
                    return Sequelize;
        }

    }
  }
});


fs.readdirSync(__dirname).filter(function(file) {
   return (file.indexOf('.') !== 0) && (file !== 'index.js');
}).forEach(function(file) {
  var model = sequelize.import(path.join(__dirname, file));
  db[model.name] = model;
});

Object.keys(db).forEach(function(modelName) {
  if ('associate' in db[modelName]) {
      db[modelName].associate(db);
  }
});

module.exports = _.extend({
  sequelize: sequelize,
  Sequelize: Sequelize
}, db);

И в файле модели

var classMethods = {
  createFromParams: function (userParams) {
    var user = this.build(userParams);

    return this.db().PromoCode.find({where: {name: user.promoCode}}).then(function (code) {
        user.credits += code.credits;
                return user.save();
    });
  }

};

module.exports = function(sequelize, DataTypes) {
  return sequelize.define("User", {
  userId: DataTypes.STRING,
}, {  tableName: 'users',
    classMethods: classMethods
 });
};

Я сделал это только для методов класса, но вы также можете сделать то же самое для методов экземпляра.

Джекоб
источник
+1 для этого прототипа classMethod, который возвращает db. Это именно та идея, которую я искал, чтобы иметь возможность загружать classMethods во время определения, но также иметь возможность ссылаться на любую модель в ClassMethod (то есть для включения отношений)
bitwit
2

Я следую официальному руководству: http://sequelizejs.com/heroku , в котором есть папка моделей, каждый модуль настраивается в отдельные файлы и есть индексный файл для их импорта и установления взаимосвязи между ними.

Рон
источник
ссылка недействительна
prisar
2

Продолжение образца модели

'use strict';
const getRole   = require('../helpers/getRole')
const library   = require('../helpers/library')
const Op        = require('sequelize').Op

module.exports = (sequelize, DataTypes) => {
  var User = sequelize.define('User', {
    AdminId: DataTypes.INTEGER,
    name: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Name must be filled !!'
        },
      }
    },
    email: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Email must be filled !!'
        },
        isUnique: function(value, next) {
          User.findAll({
            where:{
              email: value,
              id: { [Op.ne]: this.id, }
            }
          })
          .then(function(user) {
            if (user.length == 0) {
              next()
            } else {
              next('Email already used !!')
            }
          })
          .catch(function(err) {
            next(err)
          })
        }
      }
    },
    password: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Password must be filled !!'
        },
        len: {
          args: [6, 255],
          msg: 'Password at least 6 characters !!'
        }
      }
    },
    role: {
      type: DataTypes.INTEGER,
      validate: {
        customValidation: function(value, next) {
          if (value == '') {
            next('Please choose a role !!')
          } else {
            next()
          }
        }
      }
    },
    gender: {
      type: DataTypes.INTEGER,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Gender must be filled !!'
        },
      }
    },
    handphone: {
      type: DataTypes.STRING,
      validate: {
        notEmpty: {
          args: true,
          msg: 'Mobile no. must be filled !!'
        },
      }
    },
    address: DataTypes.TEXT,
    photo: DataTypes.STRING,
    reset_token: DataTypes.STRING,
    reset_expired: DataTypes.DATE,
    status: DataTypes.INTEGER
  }, {
    hooks: {
      beforeCreate: (user, options) => {
        user.password = library.encrypt(user.password)
      },
      beforeUpdate: (user, options) => {
        user.password = library.encrypt(user.password)
      }
    }
  });

  User.prototype.check_password = function (userPassword, callback) {
    if (library.comparePassword(userPassword, this.password)) {
      callback(true)
    }else{
      callback(false)
    }
  }

  User.prototype.getRole = function() {
    return getRole(this.role)
  }

  User.associate = function(models) {
    User.hasMany(models.Request)
  }

  return User;
};


Мухаммад Ариф Триманда
источник
1

Вы можете импортировать модели из других файлов с помощью sequelize.import http://sequelizejs.com/documentation#models-import

Таким образом, у вас может быть один одноэлементный модуль для продолжения, который затем загружает все другие модели.

На самом деле этот ответ очень похож на ответ пользователя 1778770.

natrixnatrix89
источник
1
это работает с круговыми зависимостями? Например, когда у модели A есть FK для модели B, а у модели be есть FK для модели A
mkoryak
1

Я ищу пример приложения nodejs, которое использует sequelize ORM.

Возможно, вас заинтересует шаблонное решение PEAN.JS.

PEAN.JS - это полнофункциональное JavaScript-решение с открытым исходным кодом, которое обеспечивает надежную отправную точку для приложений на основе PostgreSQL, Node.js, Express и AngularJS.

Проект PEAN является форком проекта MEAN.JS (не путать с MEAN.IO или общим стеком MEAN).

PEAN заменяет MongoDB и Mongoose ORM на PostgreSQL и Sequelize. Основное преимущество проекта MEAN.JS - это организация, которую он обеспечивает для стека, состоящего из множества движущихся частей.

mg1075
источник
0

Вы также можете использовать внедрение зависимостей, которое предлагает элегантное решение этой проблемы. Вот один https://github.com/justmoon/reduct

Ваге Оганесян
источник