Cartografía relacional de obxectos para fibjs
Instalar
1npm install fib-orm
Proba
1npm run ci
Soporte SGBD
- 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 cartografía 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 deste xeito:
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 engadir novas funcións, o desenvolvemento de documentos poden referirse ao nó-ORM, só precisa cambiar a chamada asíncrona a versión síncrona. Wiki .
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 bases 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 de táboas.
Definición de modelos
Ver información na wiki .
Properties
Ver información na wiki .
Instance Methods
Pásanse 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úrese de que o módulo que contén os modelos usa module.exports para publicar unha función que acepte a conexión á base de datos e logo cargue os modelos como queira.
Utilizando esta técnica, pode 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. Pode configuralos mediante axustes ou na chamada ao define
configurar 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, non se engadirá "id":
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, unha UID
e unha name
.
Tamén é posible ter teclas 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
Configúraotrue
para habilitar a caché de identidade ( Singletons ) ou define un valor de tempo de espera (en segundos);autoSave
: (predeterminado :)false
Configúrao entrue
para gardar unha instancia xusto despois de cambiar calquera propiedade;autoFetch
: (predeterminado :)false
Configúreotrue
en buscar asociacións ao buscar unha instancia da base de datos;autoFetchLimit
: (por defecto :)1
SeautoFetch
está activado, defínese cantos aros (asociacións de asociacións) desexa que busque automaticamente.
Ganchos
Ver información na wiki .
Buscar elementos
Model.getSync(id, [ options ])
Para obter un elemento específico do uso da base de datos Model.get
.
1
2var person = Person.getSync(123);
// finds person with id = 123
Model.findSync([ conditions ] [, options ] [, limit ] [, order ])
Atopar un ou máis elementos ten máis opcións, cada un pode darse sen unha orde de parámetros específica. Só options
ten que ser 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 ordenen, faino:
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 durante a busca. Está documentado na sección de encadeamento a continuación.
Model.countSync([ conditions])
Se só desexa contar o número de elementos que coinciden cunha condición, pode usar en .count()
vez de atopalos todos e contalos. Isto 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])
De xeito similar .count()
, este método só comproba 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
Array
Pódese pasar unha das propiedades para seleccionar só algunhas propiedades. Object
Tamén se acepta unha para definir as condicións.
Aquí tes un exemplo para ilustrar como usar .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 base
.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 (por exemplo, funcións matemáticas).
Chaining
Se prefires unha sintaxe menos complicada, podes encadeala .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 quere saltar só unha ou dúas propiedades, pode chamar en .omit()
vez de .only
.
A cadea 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 súa aplicación a inxección SQL. A ?
sintaxe encárgase de escapar por vostede, substituíndo de forma segura o signo de interrogación na consulta cos parámetros proporcionados. Tamén pode encadear varias where
cláusulas como necesario.
.find
, .where
& .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 obter o reconto ao final. Neste caso, ignóranse o desprazamento, 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. Ten en conta que unha eliminación encadeada non executará ningún enganche.
1
2Person.find({ surname: "Doe" }).removeSync();
// Does gone..
Tamén podes facer modificacións nas túas instancias usando métodos comúns de percorrido de matrices e gardar todo ao final. [NON SOPORTE]
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, podes facelo directamente .find()
, pero para algunhas tarefas máis complicadas isto pode ser moi útil.
Model.find()
non voltar un array para que non pode simplemente acorrentar directamente. Para iniciar o fío ten que chamar
.each()
(cun callback opcional se quere percorrer a lista). A continuación, pode utilizar as funcións comúns
.filter()
, .sort()
e .forEach()
máis dunha vez.
Ao final (ou durante o proceso ..) pode chamar a:
.countSync()
se só quere 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 de táboa). Suponse que todas as claves están concatenadas polo lóxico AND
. Considéranse que os valores coinciden exactamente, a non ser que estea pasando un Array
. Neste caso considérase unha lista coa que comparar a propiedade.
1
2{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo'
{ col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5)
Se precisas outras comparacións, tes que empregar un obxecto especial creado por algunhas funcións de axuda. 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 por defecto). Se está activado, varias consultas diferentes darán como 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 debes empregar esta función. Tamén se sabe que causa algúns problemas con relacións complexas de recuperación automática. Utilízao baixo o teu propio risco.
Pode habilitarse / desactivarse 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 (pode usar o punto flotante).
Nota : unha excepción sobre o almacenamento en caché é que non se usará se non se garda unha instancia. Por exemplo, se buscas unha persoa e a cambias, mentres non se garda, non se pasará da caché.
Creando elementos
Model.createSync(items)
Para inserir novos elementos no uso da base de datos 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 definidas no 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 logo gardar unha instancia pódese facer nunha soa chamada:
1
2
3var John = Person.getSync(1);
John.saveSync({ name: "Joe", surname: "Doe" });
console.log("saved!");
Se desexa eliminar unha instancia, faga o seguinte:
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
A IS moitos para un e relación ao mesmo, AS. Pertence.
Por exemplo: Animal.hasOne('owner', Person)
.
. Animal pode ter ter só un propietario, ten ten CAN moitos, pero a persoa Animais
animal por Will teñen que ter polo The owner_id
Property engadidos automaticamente.
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 de cadeas
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
A IS moitos para moitos relación (inclúe a táboa de unión).
Por exemplo, de: Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })
.
. O Paciente cada un pode Doutor médicos ten moitos poden Diferentes pacientes diferentes teñen teñen moitas.
Isto creará unha táboa de unión patient_doctors
cando chame a Patient.sync()
:
nome da columna | tipo |
---|---|
ID_paciente | Entero (tecla composta) |
médico_id | Entero (tecla 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 accesor neste tipo de asociación devolve 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 táboas ou coleccións diferentes. Todas as extensións estarán nunha nova táboa, onde o identificador único de cada fila é o identificador de instancia do modelo principal. Por exemplo:
1
2
3
4
5
6
7var Person = db.define("person", {
name : String
});
var PersonAddress = Person.extendsTo("address", {
street : String,
number : Number
});
Unha orde CREATE TABLE a este person
con columnas id
e name
. Extensión do creará unha táboa person_address
con columnas person_id
, street
e number
. Dispoñible nos métodos de Person
Modelo semellante a un son hasOne
Association. Vostede o No presente exemplo poder chamar .getAddress(cb)
, .setAddress(Address, cb)
, ..
Nota: non tes que gardar o resultado Person.extendsTo
. Devolve un modelo estendido. Podes usalo para consultar directamente esta táboa estendida (e incluso atopar o modelo relacionado) pero depende de ti. Se só queres acceder a ela usando o modelo orixinal só pode descartar a devolución.
Examples & options
Se tes unha relación de 1 a n, debes empregar 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 segundo o requirido na base de datos especificando a required
opción:
1Animal.hasOne("owner", Person, { required: true });
Se non se require un campo, pero debería 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 (propietario_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 ten que facerse antes de que se especifique 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 activalo 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 vez 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 usando a reverse
opción. Por exemplo, se tes unha asociación de ModelA a ModelB, podes crear un accesor en ModelB para obter instancias de ModelA. ¿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 se teñen hasMany
asociacións, xa que pode xestionar moitas ou 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(...);
Soporte de transaccións
Podes usar a función de transacción de baixo nivel para procesar a transcrición de db.
1
2
3
4
5
6db.begin();
...
if(err)
db.rollback();
else
db.commit();
Ou podes usar trans para procesalo en simpile.
1
2
3
4var result = db.trans(() => {
...
return result;
});
Engadindo adaptadores de base de datos externos
Para engadir un adaptador de base de datos externo orm
, chame ao addAdapter
método, pasando o alias para usar para conectarse con este adaptador, xunto co constructor do adaptador:
1require('orm').addAdapter('cassandra', CassandraAdapter);
Ver the documentation for creating adapters para máis detalles.