Impresionante módulo comunitario

Mapeo relacional de obxectos para fibjs

Estado de construción

Instalar

1
npm install fib-orm

Proba

1
npm run ci

Soporte DBMS

  • MySQL e MariaDB
  • SQLite

características

fib-orm engade un conxunto de métodos de versión síncrona no obxecto node-orm.

Introdución

Este é un módulo de mapeo relacional de obxectos fibjs.

Un exemplo:

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();

A versión node.js así:

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

Documentación

Fibjs non engadiu novas funcións, o desenvolvemento de documentos pode referirse a node-orm, só é necesario cambiar a chamada asíncrona á versión síncrona .

Configuración

Ver información na wiki .

Conectando

Ver información na wiki .

Modelos

Un modelo é unha abstracción sobre unha ou máis táboas de base de datos. Os modelos admiten asociacións (máis abaixo). Suponse que o nome do modelo coincide co nome da táboa.

Os modelos admiten comportamentos para acceder e manipular datos da táboa.

Definición de modelos

Ver información na wiki .

Properties

Ver información na wiki .

Instance Methods

Transmítense durante a definición do modelo.

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

Defínense directamente no modelo.

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

Cargando modelos [NON SOPORTE]

Os modelos poden estar en módulos separados. Simplemente asegúrate de que o módulo que contén os modelos utilice module.exports para publicar unha función que acepte a conexión á base de datos e, a continuación, carga os teus modelos como queiras.

Nota: usando esta técnica podes ter cargas en cascada.

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 }); };

Sincronización de modelos

Ver información na wiki .

Caída de modelos

Ver información na wiki .

Opcións avanzadas

ORM2 permítelle algúns axustes avanzados nas definicións do modelo. Podes configuralas mediante a configuración ou na chamada a definecando configures o modelo.

Por exemplo, cada instancia de modelo ten un ID único na base de datos. Esta columna da táboa engádese automaticamente e chámase "id" por defecto.
Se define a súa propia key: truecolumna, "id" non se engadirá:

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 });

O modelo de mascota terá 2 columnas, un UIDe un name.

Tamén é posible ter claves compostas:

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

Outras opcións:

  • identityCache : (predeterminado: false) Establéceo truepara activar a caché de identidade ( Singletons ) ou establece un valor de tempo de espera (en segundos);
  • autoSave : (predeterminado: false) Establéceo para truegardar unha instancia xusto despois de cambiar calquera propiedade;
  • autoFetch : (predeterminado: false) Establéceo truepara obter asociacións ao buscar unha instancia da base de datos;
  • autoFetchLimit: (predeterminado: 1) Se autoFetchestá activado, isto define cantos aros (asociacións de asociacións) quere que obteña automaticamente.

Ganchos

Ver información na wiki .

Buscando elementos

Model.getSync(id, [ options ])

Para obter un elemento específico da base de datos use Model.get.

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

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

Ao atopar un ou máis elementos ten máis opcións, cada un se pode dar sen orde de parámetros específico. Só optionsten que estar despois conditions(aínda que sexa un obxecto baleiro).

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

Se precisas ordenar os resultados porque estás limitando ou só porque queres que se clasifiquen fai:

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)

Hai máis opcións que podes pasar para atopar algo. Estas opcións pásanse nun segundo obxecto:

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

Tamén podes usar SQL en bruto cando buscas. Está documentado na sección Encadeamento a continuación.

Model.countSync([ conditions])

Se só queres contar o número de elementos que coincidan cunha condición, podes usalos .count()en lugar de atopalos todos e contalos. Isto en realidade indicará ao servidor de base de datos que faga un reconto (non se fará no propio proceso do nodo). ).

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

Model.existsSync([ conditions])

Do mesmo xeito que .count(), este método só verifica se o reconto é maior que cero ou non.

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

Aggregating Functions

Pódese pasar An Arrayde propiedades para seleccionar só algunhas propiedades. ObjectTamén se acepta An para definir condicións.

Aquí tes un exemplo para ilustrar como usalo .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()Métodos básicos

  • .limit(): pode pasar un número como límite, ou dous números como compensación e límite respectivamente
  • .order(): o mesmo queModel.find().order()

.aggregate()Métodos adicionais

  • min
  • max
  • avg
  • sum
  • count(hai un atallo para isto - Model.count)

Hai máis funcións agregadas dependendo do controlador (funcións matemáticas, por exemplo).

Chaining

Se prefires unha sintaxe menos complicada, podes encadear .find()sen dar un parámetro de devolución de chamada.

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

Se queres omitir só unha ou dúas propiedades, podes chamar .omit()en lugar de .only.

O encadeamento permite realizar consultas máis complicadas. Por exemplo, podemos buscar especificando SQL personalizado:

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

É unha mala práctica escapar manualmente dos parámetros SQL xa que é propenso a erros e expón a túa aplicación á inxección de SQL. A ?sintaxe encárgase de escapar por ti, substituíndo con seguridade o signo de interrogación na consulta cos parámetros proporcionados. Tamén podes encadear varias wherecláusulas como necesario.

.find, .wheree .allfan o mesmo; todos son intercambiables e encadeables.

Tamén podes orderou 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( ... );

Tamén podes encadear e só obter o reconto ao final. Neste caso, ignórase a compensación, o límite e a orde.

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

Tamén está dispoñible a opción de eliminar os elementos seleccionados. Teña en conta que unha eliminación encadeada non executará ningún gancho.

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

Tamén podes facer modificacións nas túas instancias usando métodos comúns de atravesamento de matriz e gardalo todo ao final. [NON COMPARTE]

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 });

Por suposto que podes facelo directamente en .find(), pero para algunhas tarefas máis complicadas isto pode ser moi útil.

Model.find()non devolve un Array polo que non podes encadear directamente. Para comezar a encadear tes que chamar .each()(cunha devolución de chamada opcional se queres percorrer a lista). Despois podes usar as funcións comúns .filter(), .sort()e .forEach()máis dunha vez.

Ao final (ou durante o proceso..) podes chamar a:

  • .countSync()se só queres saber cantos elementos hai;
  • .getSync()para recuperar a lista;
  • .saveSync()para gardar todos os cambios de elementos.

Condicións

As condicións defínense como un obxecto onde cada clave é unha propiedade (columna da táboa). Suponse que todas as claves están concatenadas polo lóxico AND. Considérase que os valores coinciden exactamente, a non ser que esteas pasando un Array. Neste caso, considérase unha lista para comparar a propiedade.

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

Se necesitas outras comparacións, tes que usar un obxecto especial creado por algunhas funcións auxiliares. Aquí tes algúns exemplos para describilo:

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)

Consultas en bruto

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

Podes usar o patrón de identidade (desactivado de forma predeterminada). Se está activado, varias consultas diferentes darán como resultado o mesmo resultado: obterás o mesmo obxecto. Se tes outros sistemas que poden cambiar a túa base de datos ou necesitas chamar a algún manual Consultas SQL, non deberías usar esta función. Tamén se sabe que causa algúns problemas con relacións complexas de obtención automática. Úsaa baixo o teu propio risco.

Pódese activar/desactivar por modelo:

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

e tamén a nivel mundial:

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

A caché de identidade pódese configurar para que caduque despois dun período de tempo pasando un número en lugar dun booleano. O número considerarase o tempo de espera da caché en segundos (podes usar coma flotante).

Nota : Unha excepción sobre o caché é que non se utilizará se non se garda unha instancia. Por exemplo, se buscas unha persoa e despois a cambias, mentres non se garda, non se pasará desde a caché.

Creando Elementos

Model.createSync(items)

Para inserir novos elementos na base de datos use 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

Actualizando elementos

Cada elemento devolto ten as propiedades que se definiron para o Modelo e tamén un par de métodos que pode usar para cambiar cada elemento.

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

A actualización e despois gardar unha instancia pódese facer nunha única chamada:

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

Se queres eliminar unha instancia, só tes que facer:

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

Validacións

Ver información na wiki .

Asociacións

Unha asociación é unha relación entre unha ou máis táboas.

hasOne

É unha relación de varios a un . É o mesmo que pertence.
Por exemplo: Animal.hasOne('owner', Person).
O animal só pode ter un propietario, pero a persoa pode ter moitos animais.
Engadirase a owner_idpropiedade automaticamente ao animal.

Estarán dispoñibles as seguintes funcións:

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

Busca en cadea

A asociación hasOne tamén é compatible coa busca en cadea. Usando o exemplo anterior, podemos facelo para acceder a unha nova instancia dun obxecto ChainFind:

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

Acceso inverso

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

engadirá o seguinte:

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

hasMany

É unha relación de moitos a moitos (inclúe a táboa de unión).
Por exemplo: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true }).
O paciente pode ter moitos médicos diferentes. Cada médico pode ter moitos pacientes diferentes.

Isto creará unha táboa de unión patient_doctorscando chames Patient.sync():

nome da columna tipo
paciente_id Enteiro (clave composta)
doutor_id Enteiro (clave composta)
por que varchar (255)

Estarán dispoñibles as seguintes funcións:

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()

Para asociar un médico a un paciente:

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

que se engadirá {patient_id: 4, doctor_id: 6, why: "remove appendix"}á táboa de unión.

getAccessor

Este accesorio deste tipo de asociación devolve un ChainFindse non pasa unha devolución de chamada. Isto significa que podes facer cousas como:

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

extendsTo

Se queres dividir propiedades opcionais en diferentes táboas ou coleccións. Cada extensión estará nunha táboa nova, onde o identificador único de cada fila é o ID de instancia principal do modelo. Por exemplo:

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

Isto creará unha táboa personcon columnas ide name. A extensión creará unha táboa person_addresscon columnas person_id, streete number. Os métodos dispoñibles no Personmodelo son similares a unha hasOne asociación. Neste exemplo poderás chamar a .getAddress(cb), .setAddress(Address, cb), ..

Nota: non tes que gardar o resultado de Person.extendsTo. Devolve un modelo estendido. Podes usalo para consultar directamente esta táboa estendida (e incluso atopar o modelo relacionado), pero iso depende de ti. Se só queres acceder a ela usando o modelo orixinal só podes descartar a devolución.

Examples & options

Se tes unha relación de 1 a n, deberías usar hasOne(pertence a) asociación.

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

Pode marcar o owner_idcampo como necesario na base de datos especificando a requiredopción:

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

Se un campo non é obrigatorio, pero debe validarse aínda que non estea presente, especifique a alwaysValidateopción (isto pode ocorrer, por exemplo, cando a validación dun campo nulo depende doutros campos do rexistro).

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

Se prefires usar outro nome para o campo (owner_id), podes cambiar este parámetro na configuración.

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

Nota: Isto debe facerse antes de especificar a asociación.

As hasManyasociacións poden ter propiedades adicionais na táboa de asociacións.

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

Se o prefires, podes activar autoFetch. Deste xeito, as asociacións obtéñense automaticamente cando obteñas ou atopas instancias dun modelo.

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

Tamén pode definir esta opción globalmente en lugar de por asociación.

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 });

As asociacións poden facer chamadas ao modelo asociado mediante a reverseopción. Por exemplo, se tes unha asociación do modelo A ao modelo B, podes crear un accesorio no modelo B para obter instancias do modelo A. ¿Confuso? Mira o seguinte exemplo.

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

Isto ten aínda máis sentido cando hai hasManyasociacións, xa que pode xestionar moitas e moitas asociacións de ambos os dous lados.

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(...);

Apoio á transacción

Podes usar a función de transacción de baixo nivel para procesar a transacción de base de datos.

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

Ou pode usar trans para procesala de forma sinxela.

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

Engadindo adaptadores de bases de datos externas

Para engadir un adaptador de base de datos externo a orm, chame ao addAdaptermétodo, pasando o alias para conectarse con este adaptador, xunto co construtor do adaptador:

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

Verthe documentation for creating adapterspara máis detalles.