Удивительный модуль сообщества

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

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

устанавливать

1
npm install fib-orm

Тест

1
npm run ci

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

  • MySQL & MariaDB
  • 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 столбец key: true , идентификатор не будет добавлен:

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 сделать Thing То же, все они взаимозаменяемы и цепные.

Вы также можете order или 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() цепочку напрямую. Чтобы начать цепочку, вы должны вызвать .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, а затем изменяете его, пока он не сохраняется, он не будет передан из Cache.

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

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!");

Validations

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

ассоциации

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

hasOne

Отношение много к одному . Это то же самое, что и отношение.
Например: Animal.hasOne('owner', Person) .
Животное может иметь только одного владельца, но Человек может иметь много животных.
У Animal будет автоматически добавлено свойство 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 также совместима по цепочке find. Используя приведенный выше пример, мы можем сделать это для доступа к новому экземпляру объекта 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() :

имя столбца тип
patient_id Integer (составной ключ)
doctor_id Integer (составной ключ)
Зачем VARCHAR (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

Этот ChainFind доступа в этом типе ассоциации возвращает 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 для более подробной информации.