Mapeo relacional de obxectos para fibjs
Instalar
1npm install fib-orm
Proba
1npm 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
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();
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
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ó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
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
Defínense directamente no modelo.
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);
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 define
cando 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: true
columna, "id" non se engadirá:
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
});
O modelo de mascota terá 2 columnas, un UID
e un name
.
Tamén é posible ter claves compostas:
1
2
3
4var Person = db.define("person", {
firstname : { type: 'text', key: true },
lastname : { type: 'text', key: true }
});
Outras opcións:
identityCache
: (predeterminado:false
) Establéceotrue
para activar a caché de identidade ( Singletons ) ou establece un valor de tempo de espera (en segundos);autoSave
: (predeterminado:false
) Establéceo paratrue
gardar unha instancia xusto despois de cambiar calquera propiedade;autoFetch
: (predeterminado:false
) Establéceotrue
para obter asociacións ao buscar unha instancia da base de datos;autoFetchLimit
: (predeterminado:1
) SeautoFetch
está 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
2var 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ó options
ten que estar despois conditions
(aínda que sexa un obxecto baleiro).
1
2var 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
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)
Hai máis opcións que podes pasar para atopar algo. Estas opcións pásanse nun segundo obxecto:
1
2var 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
2var 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
2var exists = Person.existsSync({ surname: "Doe" });
console.log("We %s Does in our db", exists ? "have" : "don't have");
Aggregating Functions
Pódese pasar An Array
de propiedades para seleccionar só algunhas propiedades. Object
Tamé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
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
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:
1Person.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 where
cláusulas como necesario.
.find
, .where
e .all
fan o mesmo; todos son intercambiables e encadeables.
Tamén podes order
ou 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( ... );
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
2var 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
2Person.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
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
});
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
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
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
5var Person = db.define('person', {
name : String
}, {
identityCache : true
});
e tamén a nivel mundial:
1
2var 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
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
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
5var 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
3var 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_id
propiedade automaticamente ao animal.
Estarán dispoñibles as seguintes funcións:
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
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:
1Animal.findByOwner({ /* options */ })
Acceso inverso
1Animal.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_doctors
cando 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
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()
Para asociar un médico a un paciente:
1patient.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 ChainFind
se non pasa unha devolución de chamada. Isto significa que podes facer cousas como:
1
2var 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
7var Person = db.define("person", {
name : String
});
var PersonAddress = Person.extendsTo("address", {
street : String,
number : Number
});
Isto creará unha táboa person
con columnas id
e name
. A extensión creará unha táboa person_address
con columnas person_id
, street
e number
. Os métodos dispoñibles no Person
modelo 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
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
Pode marcar o owner_id
campo como necesario na base de datos especificando a required
opción:
1Animal.hasOne("owner", Person, { required: true });
Se un campo non é obrigatorio, pero debe validarse aínda que non estea presente, especifique a alwaysValidate
opción (isto pode ocorrer, por exemplo, cando a validación dun campo nulo depende doutros campos do rexistro).
1Animal.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.
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: Isto debe facerse antes de especificar a asociación.
As hasMany
asociacións poden ter propiedades adicionais na táboa de asociacións.
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
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
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
Tamén pode definir esta opción globalmente en lugar de por asociación.
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
});
As asociacións poden facer chamadas ao modelo asociado mediante a reverse
opció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
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
Isto ten aínda máis sentido cando hai hasMany
asociació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
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(...);
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
6db.begin();
...
if(err)
db.rollback();
else
db.commit();
Ou pode usar trans para procesala de forma sinxela.
1
2
3
4var 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 addAdapter
método, pasando o alias para conectarse con este adaptador, xunto co construtor do adaptador:
1require('orm').addAdapter('cassandra', CassandraAdapter);
Verthe documentation for creating adapterspara máis detalles.