Mapatge relacional d'objectes per a fibjs
Instal·lar
1npm install fib-orm
Prova
1npm run ci
Suport DBMS
- MySQL i MariaDB
- SQLite
Característiques
fib-orm afegeix un conjunt de mètodes de versió síncrona a l'objecte node-orm.
Introducció
Aquest és un mòdul de mapeig relacional d'objectes fibjs.
Un exemple:
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
37var 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();
La versió node.js com aquesta:
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
49var 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ó
Fibjs no va afegir noves funcions, el desenvolupament de documents pot fer referència a node-orm, només cal canviar la trucada asíncrona a la versió síncrona .
Configuració
Veure informació a la wiki .
Connectant
Veure informació a la wiki .
Models
Un model és una abstracció sobre una o més taules de bases de dades. Els models admeten associacions (més a continuació). Se suposa que el nom del model coincideix amb el nom de la taula.
Els models admeten comportaments per accedir i manipular dades de la taula.
Definició de models
Veure informació a la wiki .
Properties
Veure informació a la wiki .
Instance Methods
Es transmeten durant la definició del model.
1
2
3
4
5
6
7
8
9
10
11
12
13var 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
Es defineixen directament sobre el model.
1
2
3
4
5
6
7
8
9var 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);
Carregant models [NO SUPORT]
Els models poden estar en mòduls separats. Simplement assegureu-vos que el mòdul que conté els models utilitzi module.exports per publicar una funció que accepti la connexió a la base de dades i, a continuació, carregueu els vostres models com vulgueu.
Nota: amb aquesta tècnica podeu tenir càrregues 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
});
};
Sincronització de models
Veure informació a la wiki .
Caiguda de models
Veure informació a la wiki .
Opcions avançades
L'ORM2 us permet alguns ajustaments avançats a les definicions del vostre model. Podeu configurar-les mitjançant la configuració o trucant a define
quan configureu el model.
Per exemple, cada instància del model té un identificador únic a la base de dades. Aquesta columna de la taula s'afegeix automàticament i s'anomena "id" per defecte.
Si definiu la vostra pròpia key: true
columna, "id" no s'afegirà:
1
2
3
4
5
6
7
8
9
10
11
12var 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
});
El model de mascota tindrà 2 columnes, una UID
i una name
.
També és possible tenir claus compostes:
1
2
3
4var Person = db.define("person", {
firstname : { type: 'text', key: true },
lastname : { type: 'text', key: true }
});
Altres opcions:
identityCache
: (per defecte:false
) Establiu-lotrue
per habilitar la memòria cau d'identitats ( Singletons ) o establiu un valor de temps d'espera (en segons);autoSave
: (per defecte:false
) Establiu-lo pertrue
desar una instància just després de canviar qualsevol propietat;autoFetch
: (per defecte:false
) Establiu-lotrue
per obtenir associacions quan obteniu una instància de la base de dades;autoFetchLimit
: (per defecte:1
) SiautoFetch
està habilitat, això defineix quants cèrcols (associacions d'associacions) voleu que obtingui automàticament.
Ganxos
Veure informació a la wiki .
Trobar objectes
Model.getSync(id, [ options ])
Per obtenir un element específic de la base de dades, utilitzeu Model.get
.
1
2var person = Person.getSync(123);
// finds person with id = 123
Model.findSync([ conditions ] [, options ] [, limit ] [, order ])
Trobar un o més elements té més opcions, cadascun es pot donar sense un ordre de paràmetres específic. Només options
ha d'estar després conditions
(encara que sigui un objecte buit).
1
2var people = Person.findSync({ name: "John", surname: "Doe" }, 3);
// finds people with name='John' AND surname='Doe' and returns the first 3
Si necessiteu ordenar els resultats perquè esteu limitant o simplement perquè els voleu ordenar, feu el següent:
1
2
3
4
5
6var 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)
Hi ha més opcions que pots passar per trobar alguna cosa. Aquestes opcions es passen en un segon objecte:
1
2var people = Person.findSync({ surname: "Doe" }, { offset: 2 });
// finds people with surname='Doe', skips the first 2 and returns the others
També podeu utilitzar SQL sense processar quan cerqueu. Està documentat a la secció Encadenament a continuació.
Model.countSync([ conditions])
Si només voleu comptar el nombre d'elements que coincideixen amb una condició, podeu utilitzar-los .count()
en lloc de trobar-los tots i comptar. Això indicarà al servidor de bases de dades que faci un recompte (no es farà en el procés del node en si. ).
1
2var count = Person.countSync({ surname: "Doe" });
console.log("We have %d Does in our db", count);
Model.existsSync([ conditions])
De manera similar a .count()
, aquest mètode només comprova si el recompte és superior a zero o no.
1
2var exists = Person.existsSync({ surname: "Doe" });
console.log("We %s Does in our db", exists ? "have" : "don't have");
Aggregating Functions
Array
Es pot passar una de propietats per seleccionar només unes poques propietats. Object
També s'accepta una per definir condicions.
Aquí teniu un exemple per il·lustrar com utilitzar-lo .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ètodes base
.limit()
: podeu passar un nombre com a límit, o dos números com a compensació i límit respectivament.order()
: el mateix queModel.find().order()
.aggregate()
Mètodes addicionals
min
max
avg
sum
count
(hi ha una drecera a això -Model.count
)
Hi ha més funcions agregades segons el controlador (funcions matemàtiques per exemple).
Chaining
Si preferiu una sintaxi menys complicada, podeu encadenar .find()
sense donar un paràmetre de devolució de trucada.
1
2
3var 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
Si només voleu ometre una o dues propietats, podeu trucar .omit()
en comptes de .only
.
L'encadenament permet fer consultes més complicades. Per exemple, podem cercar especificant un SQL personalitzat:
1Person.find({ age: 18 }).where("LOWER(surname) LIKE ?", ['dea%']).allSync( ... );
És una mala pràctica escapar manualment dels paràmetres SQL, ja que és propens a errors i exposa la vostra aplicació a la injecció d'SQL. La ?
sintaxi s'encarrega d'escapar per vosaltres, substituint de manera segura el signe d'interrogació de la consulta amb els paràmetres proporcionats. També podeu encadenar diverses where
clàusules com a necessari.
.find
, .where
i .all
fan el mateix; tots són intercanviables i encadenants.
També pots order
o orderRaw
:
1
2
3Person.find({ age: 18 }).order('-name').allSync( ... );
// see the 'Raw queries' section below for more details
Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).allSync( ... );
També podeu encadenar i obtenir el recompte al final. En aquest cas, s'ignoren el desplaçament, el límit i l'ordre.
1
2var people = Person.find({ surname: "Doe" }).countSync();
// people = number of people with surname="Doe"
També està disponible l'opció d'eliminar els elements seleccionats. Tingueu en compte que una eliminació encadenada no executarà cap ganxo.
1
2Person.find({ surname: "Doe" }).removeSync();
// Does gone..
També podeu fer modificacions a les vostres instàncies mitjançant mètodes habituals de travessa de matriu i desar-ho tot al final. [NO SUPORT]
1
2
3
4
5
6
7
8
9
10
11
12
13Person.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
});
Per descomptat, podeu fer-ho directament a .find()
, però per a algunes tasques més complicades això pot ser molt útil.
Model.find()
no retorna una matriu, de manera que no podeu encadenar directament. Per començar a encadenar, heu de trucar
.each()
(amb una devolució de trucada opcional si voleu recórrer la llista). Després podeu utilitzar les funcions comunes
.filter()
, .sort()
i .forEach()
més d'una vegada.
Al final (o durant el procés...) podeu trucar a:
.countSync()
si només voleu saber quants articles hi ha;.getSync()
per recuperar la llista;.saveSync()
per desar tots els canvis d'elements.
Condicions
Les condicions es defineixen com un objecte on cada clau és una propietat (columna de la taula). Se suposa que totes les claus s'han de concatenar amb el lògic AND
. Es considera que els valors coincideixen exactament, tret que esteu passant un Array
. En aquest cas es considera una llista per comparar la propietat.
1
2{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo'
{ col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5)
Si necessiteu altres comparacions, heu d'utilitzar un objecte especial creat per algunes funcions d'ajuda. Aquí teniu uns quants exemples per descriure-ho:
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)
Consultes en brut
1
2
3
4
5
6
7
8
9
10
11
12
13var 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
Podeu utilitzar el patró d'identitat (desactivat de manera predeterminada). Si està activat, diverses consultes diferents donaran com a resultat el mateix resultat: obtindreu el mateix objecte. Si teniu altres sistemes que poden canviar la vostra base de dades o necessiteu trucar a algun manual Consultes SQL, no hauríeu d'utilitzar aquesta funció. També se sap que causa alguns problemes amb relacions complexes de recuperació automàtica. Utilitzeu-la sota el vostre propi risc.
Es pot activar/desactivar per model:
1
2
3
4
5var Person = db.define('person', {
name : String
}, {
identityCache : true
});
i també a nivell mundial:
1
2var db = orm.connectSync('...');
db.settings.set('instance.identityCache', true);
La memòria cau d'identitats es pot configurar perquè caduqui al cap d'un període de temps passant un número en lloc d'un booleà. El nombre es considerarà el temps d'espera de la memòria cau en segons (podeu utilitzar el punt flotant).
Nota : una excepció de l'emmagatzematge a la memòria cau és que no s'utilitzarà si no es desa una instància. Per exemple, si obteniu una persona i després la canvieu, mentre que no es desi, no es passarà de la memòria cau.
Creació d'Items
Model.createSync(items)
Per inserir nous elements a la base de dades utilitzeu Model.create
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var items = Person.createSync([
{
name: "John",
surname: "Doe",
age: 25,
male: true
},
{
name: "Liza",
surname: "Kollan",
age: 19,
male: false
}
]);
// items - array of inserted items
Actualització d'elements
Cada element retornat té les propietats que es van definir al model i també un parell de mètodes que podeu utilitzar per canviar cada element.
1
2
3
4
5var John = Person.getSync(1);
John.name = "Joe";
John.surname = "Doe";
John.saveSync();
console.log("saved!");
L'actualització i després desar una instància es pot fer en una sola trucada:
1
2
3var John = Person.getSync(1);
John.saveSync({ name: "Joe", surname: "Doe" });
console.log("saved!");
Si voleu eliminar una instància, feu el següent:
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!");
Validacions
Veure informació a la wiki .
Associacions
Una associació és una relació entre una o més taules.
hasOne
És una relació de molts a un . És el mateix que pertany a.
Per exemple: Animal.hasOne('owner', Person)
.
L'animal només pot tenir un propietari, però la persona pot tenir molts animals.
L'animal s'afegirà owner_id
automàticament la propietat.
Les funcions següents estaran disponibles:
1
2
3
4animal.getOwnerSync() // Gets owner
animal.setOwnerSync(person) // Sets owner_id
animal.hasOwnerSync() // Checks if owner exists
animal.removeOwnerSync() // Sets owner_id to 0
Troba en cadena
L'associació hasOne també és compatible amb la recerca en cadena. Utilitzant l'exemple anterior, podem fer això per accedir a una nova instància d'un objecte ChainFind:
1Animal.findByOwner({ /* options */ })
Accés invers
1Animal.hasOne('owner', Person, {reverse: 'pets'})
afegirà el següent:
1
2
3
4
5
6// Instance methods
person.getPetsSync(function..)
person.setPetsSync(cat, function..)
// Model methods
Person.findByPets({ /* options */ }) // returns ChainFind object
hasMany
És una relació de molts a molts
(inclou la taula d'unió)
Per exemple: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })
.
El pacient pot tenir molts metges diferents. Cada metge pot tenir molts pacients diferents.
Això crearà una taula d'unió patient_doctors
quan truqueu a Patient.sync()
:
nom de columna | tipus |
---|---|
pacient_id | Enter (clau composta) |
doctor_id | Enter (clau composta) |
Per què | varchar (255) |
Les funcions següents estaran disponibles:
1
2
3
4
5
6
7
8
9
10
11
12patient.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()
Per associar un metge a un pacient:
1patient.addDoctorSync(surgeon, {why: "remove appendix"})
que s'afegirà {patient_id: 4, doctor_id: 6, why: "remove appendix"}
a la taula d'unió.
getAccessor
Aquest descriptor d'accés en aquest tipus d'associació retorna ChainFind
si no passa una devolució de trucada. Això vol dir que podeu fer coses com ara:
1
2var doctors = patient.getDoctors().order("name").offset(1).runSync());
// ... all doctors, ordered by name, excluding first one
extendsTo
Si voleu dividir propietats opcionals en diferents taules o col·leccions. Cada extensió es trobarà en una taula nova, on l'identificador únic de cada fila és l'identificador principal de la instància del model. Per exemple:
1
2
3
4
5
6
7var Person = db.define("person", {
name : String
});
var PersonAddress = Person.extendsTo("address", {
street : String,
number : Number
});
Això crearà una taula person
amb columnes id
i name
. L'extensió crearà una taula person_address
amb columnes person_id
, street
i number
. Els mètodes disponibles al Person
model són similars a una hasOne
associació. En aquest exemple podríeu cridar a .getAddress(cb)
, .setAddress(Address, cb)
, ..
Nota: no cal que deseu el resultat de Person.extendsTo
. Retorna un model estès. Podeu utilitzar-lo per consultar directament aquesta taula ampliada (i fins i tot trobar el model relacionat), però això depèn de vosaltres. Si només voleu accedir-hi fent servir el model original només podeu descartar la devolució.
Examples & options
Si teniu una relació d'1 a n, hauríeu d'utilitzar hasOne
(pertany a) l'associació.
1
2
3
4
5
6
7
8
9
10
11
12
13var 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
Podeu marcar el owner_id
camp com a requerit a la base de dades especificant l' required
opció:
1Animal.hasOne("owner", Person, { required: true });
Si un camp no és obligatori, però s'ha de validar encara que no estigui present, especifiqueu l' alwaysValidate
opció (això pot passar, per exemple, quan la validació d'un camp nul depèn d'altres camps del registre).
1Animal.hasOne("owner", Person, { required: false, alwaysValidate: true });
Si preferiu utilitzar un altre nom per al camp (owner_id), podeu canviar aquest paràmetre a la configuració.
1db.settings.set("properties.association_key", "{field}_{name}"); // {name} will be replaced by 'owner' and {field} will be replaced by 'id' in this case
Nota: això s'ha de fer abans d'especificar l'associació.
Les hasMany
associacions poden tenir propietats addicionals a la taula d'associacions.
1
2
3
4
5
6
7
8
9
10
11var 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
Si ho preferiu, podeu activar autoFetch
. D'aquesta manera, les associacions s'obtenen automàticament quan obteniu o trobeu instàncies d'un model.
1
2
3
4
5
6
7
8
9
10
11
12var 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
També podeu definir aquesta opció de manera global en lloc de per associació.
1
2
3
4
5
6
7
8
9
10var Person = db.define('person', {
name : String
}, {
autoFetch : true
});
Person.hasMany("friends", {
rate : Number
}, {
key: true
});
Les associacions poden fer trucades al Model associat mitjançant l' reverse
opció. Per exemple, si teniu una associació del ModelA al ModelB, podeu crear un descriptor d'accés al ModelB per obtenir instàncies del ModelA. Teniu confós? Mireu l'exemple següent.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var 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
Això té encara més sentit quan tens hasMany
associacions, ja que pots gestionar moltes o moltes
associacions d'ambdues parts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var 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(...);
Suport a la transacció
Podeu utilitzar la funció de transacció de baix nivell per processar la transacció de base de dades.
1
2
3
4
5
6db.begin();
...
if(err)
db.rollback();
else
db.commit();
O podeu utilitzar trans per processar-lo de manera senzilla.
1
2
3
4var result = db.trans(() => {
...
return result;
});
Afegir adaptadors de bases de dades externes
Per afegir un adaptador de base de dades extern a orm
, truqueu al addAdapter
mètode, passant l'àlies que s'utilitzarà per connectar amb aquest adaptador, juntament amb el constructor de l'adaptador:
1require('orm').addAdapter('cassandra', CassandraAdapter);
Vegeuthe documentation for creating adaptersper a més detalls.