Потрясающий модуль сообщества

Реляционное сопоставление объектов для fibjs

Статус сборки

Установить

1
npm install fib-orm

Тест

1
npm run ci

Поддержка СУБД

  • MySQL и МарияДБ
  • SQLite

Функции

fib-orm добавляет набор методов синхронной версии для объекта node-orm.

Введение

Это модуль реляционного отображения объектов fibjs.

Пример:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
var orm = require("fib-orm"); var db = orm.connectSync("mysql://username:password@host/database"); var Person = db.define("person", { name : String, surname : String, age : Number, // FLOAT male : Boolean, continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type photo : Buffer, // BLOB/BINARY data : Object // JSON encoded }, { methods: { fullName: function () { return this.name + ' ' + this.surname; } }, validations: { age: orm.enforce.ranges.number(18, undefined, "under-age") } }); // add the table to the database db.syncSync(); // add a row to the person table Person.createSync({ id: 1, name: "John", surname: "Doe", age: 27 }); // query the person table by surname var people = Person.findSync({ surname: "Doe" }); // SQL: "SELECT * FROM person WHERE surname = 'Doe'" console.log("People found: %d", people.length); console.log("First person: %s, age %d", people[0].fullName(), people[0].age); people[0].age = 16; people[0].saveSync();

Версия node.js выглядит следующим образом:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
var orm = require("orm"); orm.connect("mysql://username:password@host/database", function (err, db) { if (err) throw err; var Person = db.define("person", { name : String, surname : String, age : Number, // FLOAT male : Boolean, continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type photo : Buffer, // BLOB/BINARY data : Object // JSON encoded }, { methods: { fullName: function () { return this.name + ' ' + this.surname; } }, validations: { age: orm.enforce.ranges.number(18, undefined, "under-age") } }); // add the table to the database db.sync(function(err) { if (err) throw err; // add a row to the person table Person.create({ id: 1, name: "John", surname: "Doe", age: 27 }, function(err) { if (err) throw err; // query the person table by surname Person.find({ surname: "Doe" }, function (err, people) { // SQL: "SELECT * FROM person WHERE surname = 'Doe'" if (err) throw err; console.log("People found: %d", people.length); console.log("First person: %s, age %d", people[0].fullName(), people[0].age); people[0].age = 16; people[0].save(function (err) { // err.msg = "under-age"; }); }); }); }); });

Документация

Fibjs не добавил новых функций, при разработке документов можно обращаться к node-orm, нужно только изменить асинхронный вызов на синхронную версию wiki .

Настройки

Смотрите информацию в вики .

Подключение

Смотрите информацию в вики .

Модели

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

Модели поддерживают поведение для доступа к данным таблицы и управления ими.

Определение моделей

Смотрите информацию в вики .

Properties

Смотрите информацию в вики .

Instance Methods

Передаются во время определения модели.

1 2 3 4 5 6 7 8 9 10 11 12 13
var Person = db.define('person', { name : String, surname : String }, { methods: { fullName: function () { return this.name + ' ' + this.surname; } } }); var person = Person.getSync(4); console.log( person.fullName() );

Model Methods

Определяются непосредственно на модели.

1 2 3 4 5 6 7 8 9
var Person = db.define('person', { name : String, height : { type: 'integer' } }); Person.tallerThan = function(height) { return this.findSync({ height: orm.gt(height) }); }; var tallPeople = Person.tallerThan( 192);

Загрузка моделей [НЕ ПОДДЕРЖИВАЕТСЯ]

Модели могут находиться в отдельных модулях. Просто убедитесь, что модуль, содержащий модели, использует модуль Module.exports для публикации функции, которая принимает соединение с базой данных, а затем загружайте модели так, как вам нравится.

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// your main file (after connecting) db.loadSync("./models"); // loaded! var Person = db.models.person; var Pet = db.models.pet; // models.js module.exports = function (db) { db.loadSync("./models-extra"); db.define('person', { name : String }); }; // models-extra.js module.exports = function (db) { db.define('pet', { name : String }); };

Синхронизация моделей

Смотрите информацию в вики .

Удаление моделей

Смотрите информацию в вики .

Расширенные настройки

ORM2 позволяет вам выполнять некоторые дополнительные настройки определений вашей модели. Вы можете настроить их через настройки или при вызове defineпри настройке модели.

Например, каждый экземпляр модели имеет уникальный идентификатор в базе данных. Этот столбец таблицы добавляется автоматически и по умолчанию называется "id".
Если вы определите свой собственный key: trueстолбец, "id" не будет добавлен:

1 2 3 4 5 6 7 8 9 10 11 12
var Person = db.define("person", { personId : { type: 'serial', key: true }, name : String }); // You can also change the default "id" property name globally: db.settings.set("properties.primary_key", "UID"); // ..and then define your Models var Pet = db.define("pet", { name : String });

Модель домашнего животного будет иметь 2 столбца: UIDи name.

Также возможно иметь составные ключи:

1 2 3 4
var Person = db.define("person", { firstname : { type: 'text', key: true }, lastname : { type: 'text', key: true } });

Другие варианты:

  • identityCache : (по умолчанию: false) Установите его trueдля включения кэша идентификаторов ( Singletons ) или установите значение таймаута (в секундах);
  • autoSave : (по умолчанию: false) Установите сохранение trueэкземпляра сразу после изменения любого свойства;
  • autoFetch : (по умолчанию: false) Установите trueдля извлечения ассоциаций при получении экземпляра из базы данных;
  • autoFetchLimit: (по умолчанию: 1) Если autoFetchэтот параметр включен, он определяет, сколько пялец (ассоциаций ассоциаций) вы хотите автоматически получить.

Крючки

Смотрите информацию в вики .

Нахождение предметов

Model.getSync(id, [ options ])

Чтобы получить определенный элемент из базы данных, используйте Model.get.

1 2
var person = Person.getSync(123); // finds person with id = 123

Model.findSync([ conditions ] [, options ] [, limit ] [, order ])

Поиск одного или нескольких элементов имеет больше возможностей, каждый из которых не может быть указан в определенном порядке параметров. Только optionsдолжен быть после conditions(даже если это пустой объект).

1 2
var people = Person.findSync({ name: "John", surname: "Doe" }, 3); // finds people with name='John' AND surname='Doe' and returns the first 3

Если вам нужно отсортировать результаты из-за ограничений или просто потому, что вы хотите их отсортировать, сделайте следующее:

1 2 3 4 5 6
var people = Person.findSync({ surname: "Doe" }, "name"); // finds people with surname='Doe' and returns sorted by name ascending people = Person.findSync({ surname: "Doe" }, [ "name", "Z" ]); // finds people with surname='Doe' and returns sorted by name descending // ('Z' means DESC; 'A' means ASC - default)

Есть и другие параметры, которые вы можете передать, чтобы что-то найти.Эти параметры передаются во втором объекте:

1 2
var people = Person.findSync({ surname: "Doe" }, { offset: 2 }); // finds people with surname='Doe', skips the first 2 and returns the others

Вы также можете использовать необработанный SQL при поиске. Это описано в разделе «Цепочки» ниже.

Model.countSync([ conditions])

Если вы просто хотите подсчитать количество элементов, соответствующих условию, вы можете просто использовать .count()вместо того, чтобы искать их все и подсчитывать. Это фактически скажет серверу базы данных выполнить подсчет (это не будет сделано в самом процессе узла). ).

1 2
var count = Person.countSync({ surname: "Doe" }); console.log("We have %d Does in our db", count);

Model.existsSync([ conditions])

Подобно .count(), этот метод просто проверяет, больше ли счетчик нуля или нет.

1 2
var exists = Person.existsSync({ surname: "Doe" }); console.log("We %s Does in our db", exists ? "have" : "don't have");

Aggregating Functions

Можно передать несколько Arrayсвойств, чтобы выбрать только несколько свойств, а Objectтакже определить условия.

Вот пример, иллюстрирующий, как использовать .groupBy():

1 2 3
//The same as "select avg(weight), age from person where country='someCountry' group by age;" var stats = Person.aggregate(["age"], { country: "someCountry" }).avg("weight").groupBy("age").getSync(); // stats is an Array, each item should have 'age' and 'avg_weight'

Базовые .aggregate()методы

  • .limit(): вы можете передать число в качестве ограничения или два числа в качестве смещения и ограничения соответственно.
  • .order(): такой же какModel.find().order()

Дополнительные .aggregate()методы

  • min
  • max
  • avg
  • sum
  • count(есть ярлык для этого - Model.count)

В зависимости от драйвера существует больше агрегатных функций (например, математические функции).

Chaining

Если вы предпочитаете менее сложный синтаксис, вы можете создать цепочку .find(), не указывая параметр обратного вызова.

1 2 3
var people = Person.find({ surname: "Doe" }).limit(3).offset(2).only("name", "surname").runSync(); // finds people with surname='Doe', skips first 2 and limits to 3 elements, // returning only 'name' and 'surname' properties

Если вы хотите пропустить только одно или два свойства, вы можете .omit()вместо этого вызвать .only.

Цепочка позволяет выполнять более сложные запросы. Например, мы можем выполнить поиск, указав собственный SQL:

1
Person.find({ age: 18 }).where("LOWER(surname) LIKE ?", ['dea%']).allSync( ... );

Экранировать параметры SQL вручную — плохая практика, так как это чревато ошибками и подвергает ваше приложение SQL-инъекции. Синтаксис ?позаботится об экранировании за вас, безопасно заменяя вопросительный знак в запросе предоставленными параметрами. Вы также можете объединить несколько whereпредложений в цепочку, например нужный.

.find, .where& .allделают одно и то же; все они взаимозаменяемы и могут быть объединены в цепочки.

Вы также можете orderили orderRaw:

1 2 3
Person.find({ age: 18 }).order('-name').allSync( ... ); // see the 'Raw queries' section below for more details Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).allSync( ... );

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

1 2
var people = Person.find({ surname: "Doe" }).countSync(); // people = number of people with surname="Doe"

Также доступна опция удаления выбранных элементов. Обратите внимание, что при последовательном удалении не будут запускаться какие-либо перехватчики.

1 2
Person.find({ surname: "Doe" }).removeSync(); // Does gone..

Вы также можете вносить изменения в свои экземпляры, используя распространенные методы обхода массива, и в конце сохранять все.[НЕ ПОДДЕРЖИВАЕТСЯ]

1 2 3 4 5 6 7 8 9 10 11 12 13
Person.find({ surname: "Doe" }).each(function (person) { person.surname = "Dean"; }).save(function (err) { // done! }); Person.find({ surname: "Doe" }).each().filter(function (person) { return person.age >= 18; }).sort(function (person1, person2) { return person1.age < person2.age; }).get(function (people) { // get all people with at least 18 years, sorted by age });

Конечно, вы можете сделать это прямо на .find(), но для некоторых более сложных задач это может быть очень полезно.

Model.find()не возвращает массив, поэтому вы не можете просто создать цепочку напрямую. Чтобы начать цепочку, вам нужно вызвать .each()(с необязательным обратным вызовом, если вы хотите пройти по списку). Затем вы можете использовать общие функции .filter(), .sort()и .forEach()более одного раза.

В конце (или в процессе...) вы можете позвонить:

  • .countSync()если вы просто хотите узнать, сколько предметов;
  • .getSync()получить список;
  • .saveSync()чтобы сохранить все изменения элемента.

Условия

Условия определяются как объект, где каждый ключ является свойством (столбец таблицы). Предполагается, что все ключи должны быть объединены логическим значением AND. Значения считаются точно совпадающими, если только вы не передаете Array. В этом случае считается, что список для сравнения объекта недвижимости.

1 2
{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo' { col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5)

Если вам нужны другие сравнения, вам придется использовать специальный объект, созданный некоторыми вспомогательными функциями. Вот несколько примеров для его описания:

1 2 3 4 5 6 7 8 9 10 11
{ col1: orm.eq(123) } // `col1` = 123 (default) { col1: orm.ne(123) } // `col1` <> 123 { col1: orm.gt(123) } // `col1` > 123 { col1: orm.gte(123) } // `col1` >= 123 { col1: orm.lt(123) } // `col1` < 123 { col1: orm.lte(123) } // `col1` <= 123 { col1: orm.between(123, 456) } // `col1` BETWEEN 123 AND 456 { col1: orm.not_between(123, 456) } // `col1` NOT BETWEEN 123 AND 456 { col1: orm.like(12 + "%") } // `col1` LIKE '12%' { col1: orm.not_like(12 + "%") } // `col1` NOT LIKE '12%' { col1: orm.not_in([1, 4, 8]) } // `col1` NOT IN (1, 4, 8)

Необработанные запросы

1 2 3 4 5 6 7 8 9 10 11 12 13
var data = db.driver.execQuerySync("SELECT id, email FROM user") // You can escape identifiers and values. // For identifier substitution use: ?? // For value substitution use: ? var data = db.driver.execQuerySync( "SELECT user.??, user.?? FROM user WHERE user.?? LIKE ? AND user.?? > ?", ['id', 'name', 'name', 'john', 'id', 55]) // Identifiers don't need to be scaped most of the time var data = db.driver.execQuerySync( "SELECT user.id, user.name FROM user WHERE user.name LIKE ? AND user.id > ?", ['john', 55])

Identity pattern

Вы можете использовать шаблон идентификации (по умолчанию отключен). Если этот параметр включен, несколько разных запросов приведут к одному и тому же результату - вы получите один и тот же объект. Если у вас есть другие системы, которые могут изменить вашу базу данных, или вам нужно вызвать какое-то руководство SQL-запросы, вам не следует использовать эту функцию. Также известно, что она может вызвать некоторые проблемы со сложными связями автоматической выборки. Используйте ее на свой страх и риск.

Его можно включить/отключить для каждой модели:

1 2 3 4 5
var Person = db.define('person', { name : String }, { identityCache : true });

а также глобально:

1 2
var db = orm.connectSync('...'); db.settings.set('instance.identityCache', true);

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

Примечание . Единственным исключением из кэширования является то, что оно не будет использоваться, если экземпляр не сохранен. Например, если вы извлекаете Person, а затем меняете его, пока он не сохранится, он не будет передан из кэша.

Создание предметов

Model.createSync(items)

Чтобы вставить новые элементы в базу данных, используйте Model.create.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
var items = Person.createSync([ { name: "John", surname: "Doe", age: 25, male: true }, { name: "Liza", surname: "Kollan", age: 19, male: false } ]); // items - array of inserted items

Обновление элементов

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

1 2 3 4 5
var John = Person.getSync(1); John.name = "Joe"; John.surname = "Doe"; John.saveSync(); console.log("saved!");

Обновление и последующее сохранение экземпляра можно выполнить одним вызовом:

1 2 3
var John = Person.getSync(1); John.saveSync({ name: "Joe", surname: "Doe" }); console.log("saved!");

Если вы хотите удалить экземпляр, просто выполните:

1 2 3 4
// you could do this without even fetching it, look at Chaining section above var John = Person.getSync(1); John.removeSync(); console.log("removed!");

Валидации

Смотрите информацию в вики .

Ассоциации

Ассоциация — это связь между одной или несколькими таблицами.

hasOne

Отношение « многие к одному» . Это то же самое, что и «принадлежит».
Например: Animal.hasOne('owner', Person).
Животное может иметь только одного владельца, но у человека может быть много животных. Свойство
«Животное» будет owner_idдобавлено автоматически.

Станут доступны следующие функции:

1 2 3 4
animal.getOwnerSync() // Gets owner animal.setOwnerSync(person) // Sets owner_id animal.hasOwnerSync() // Checks if owner exists animal.removeOwnerSync() // Sets owner_id to 0

Цепной поиск

Ассоциация hasOne также совместима с поиском по цепочке. Используя приведенный выше пример, мы можем сделать это для доступа к новому экземпляру объекта ChainFind:

1
Animal.findByOwner({ /* options */ })

Обратный доступ

1
Animal.hasOne('owner', Person, {reverse: 'pets'})

добавим следующее:

1 2 3 4 5 6
// Instance methods person.getPetsSync(function..) person.setPetsSync(cat, function..) // Model methods Person.findByPets({ /* options */ }) // returns ChainFind object

hasMany

Отношение многие-ко-многим (включая объединяющую таблицу).Например
: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })
пациента может быть много разных врачей.У каждого врача может быть много разных пациентов.

Это создаст таблицу соединения patient_doctorsпри вызове Patient.sync():

имя столбца тип
идентификатор_пациента Целое число (составной ключ)
доктор_ид Целое число (составной ключ)
почему варчар(255)

Будут доступны следующие функции:

1 2 3 4 5 6 7 8 9 10 11 12
patient.getDoctorsSync() // List of doctors patient.addDoctorsSync(docs) // Adds entries to join table patient.setDoctorsSync(docs) // Removes existing entries in join table, adds new ones patient.hasDoctorsSync(docs) // Checks if patient is associated to specified doctors patient.removeDoctorsSync(docs) // Removes specified doctors from join table doctor.getPatientsSync() etc... // You can also do: patient.doctors = [doc1, doc2]; patient.saveSync()

Чтобы привязать врача к пациенту:

1
patient.addDoctorSync(surgeon, {why: "remove appendix"})

который добавит {patient_id: 4, doctor_id: 6, why: "remove appendix"}в таблицу соединений.

getAccessor

Этот метод доступа в этом типе ассоциации возвращает a, ChainFindесли не передает обратный вызов. Это означает, что вы можете делать такие вещи, как:

1 2
var doctors = patient.getDoctors().order("name").offset(1).runSync()); // ... all doctors, ordered by name, excluding first one

extendsTo

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

1 2 3 4 5 6 7
var Person = db.define("person", { name : String }); var PersonAddress = Person.extendsTo("address", { street : String, number : Number });

Это создаст таблицу personсо столбцами idи name. Расширение создаст таблицу person_addressсо столбцами person_idи street. numberДоступные в модели методы Personаналогичны ассоциации hasOne . В этом примере вы сможете вызвать .getAddress(cb), .setAddress(Address, cb), ..

Примечание: вам не нужно сохранять результат из Person.extendsTo. Он возвращает расширенную модель. Вы можете использовать его для прямого запроса к этой расширенной таблице (и даже найти связанную модель), но это зависит от вас. Если вы хотите только получить к ней доступ используя исходную модель, вы можете просто отказаться от возврата.

Examples & options

Если у вас есть отношение от 1 до n, вам следует использовать hasOneассоциацию (принадлежит).

1 2 3 4 5 6 7 8 9 10 11 12 13
var Person = db.define('person', { name : String }); var Animal = db.define('animal', { name : String }); Animal.hasOne("owner", Person); // creates column 'owner_id' in 'animal' table // get animal with id = 123 var animal = Animal.getSync(123); // animal is the animal model instance, if found var person = animal.getOwnerSync(); // if animal has really an owner, person points to it

Вы можете пометить owner_idполе как обязательное в базе данных, указав опцию required:

1
Animal.hasOne("owner", Person, { required: true });

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

1
Animal.hasOne("owner", Person, { required: false, alwaysValidate: true });

Если вы предпочитаете использовать другое имя для поля (owner_id), вы можете изменить этот параметр в настройках.

1
db.settings.set("properties.association_key", "{field}_{name}"); // {name} will be replaced by 'owner' and {field} will be replaced by 'id' in this case

Примечание. Это необходимо сделать до указания ассоциации.

Ассоциации hasManyмогут иметь дополнительные свойства в таблице ассоциаций.

1 2 3 4 5 6 7 8 9 10 11
var Person = db.define('person', { name : String }); Person.hasMany("friends", { rate : Number }, {}, { key: true }); var John = Person.getSync(123); var friends = John.getFriendsSync(); // assumes rate is another column on table person_friends // you can access it by going to friends[N].extra.rate

Если вы предпочитаете, вы можете активировать autoFetch, Таким образом, ассоциации автоматически извлекаются, когда вы получаете или находите экземпляры модели.

1 2 3 4 5 6 7 8 9 10 11 12
var Person = db.define('person', { name : String }); Person.hasMany("friends", { rate : Number }, { key : true, // Turns the foreign keys in the join table into a composite key autoFetch : true }); var John = Person.getSync(123); // no need to do John.getFriends() , John already has John.friends Array

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

1 2 3 4 5 6 7 8 9 10
var Person = db.define('person', { name : String }, { autoFetch : true }); Person.hasMany("friends", { rate : Number }, { key: true });

С помощью этой опции ассоциации могут выполнять вызовы связанной модели reverse. Например, если у вас есть ассоциация ModelA с ModelB, вы можете создать метод доступа в ModelB для получения экземпляров из ModelA. Сбивает с толку? Посмотрите на следующий пример.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
var Pet = db.define('pet', { name : String }); var Person = db.define('person', { name : String }); Pet.hasOne("owner", Person, { reverse : "pets" }); var pets = Person(4).getPetsSync(); // although the association was made on Pet, // Person will have an accessor (getPets) // // In this example, ORM will fetch all pets // whose owner_id = 4

Это имеет еще больший смысл при наличии hasManyассоциаций, поскольку вы можете управлять ассоциациями «многие ко многим» с обеих сторон.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
var Pet = db.define('pet', { name : String }); var Person = db.define('person', { name : String }); Person.hasMany("pets", Pet, { bought : Date }, { key : true, reverse : "owners" }); Person(1).getPetsSync(...); Pet(2).getOwnersSync(...);

Сопровождение сделок

Вы можете использовать функцию транзакции низкого уровня для обработки транзакции базы данных.

1 2 3 4 5 6
db.begin(); ... if(err) db.rollback(); else db.commit();

Или вы можете использовать trans для упрощения его обработки.

1 2 3 4
var result = db.trans(() => { ... return result; });

Добавление адаптеров внешних баз данных

Чтобы добавить адаптер внешней базы данных в orm, вызовите addAdapterметод, передав псевдоним, который будет использоваться для подключения к этому адаптеру, а также конструктор адаптера:

1
require('orm').addAdapter('cassandra', CassandraAdapter);

Видетьthe documentation for creating adaptersБольше подробностей.