$emails = array(
array(
'_id' => 1,
'id_usuarios' => 1,
'email' => 'email1@exemplo.com'
),
array(
'_id' => 2,
'id_usuarios' => 1,
'email' => 'email2@exemplo.com'
)
);
No código 1 temos 2 matrizes, a primeira contém uma lista de usuários e na segunda uma lista de emails para cada usuário; Para relacionar a matriz
de usuários com a matriz de emails, utilizamos uma chave na matriz de emails chamada id_usuarios.
Código 2:
<?php
$usuarios = array(
array(
'_id' => 1,
'nome' => 'João Batista Neto',
'emails' => array( 'email1@exemplo.com' , 'emails2@exemplo.com' )
)
);
Se você fosse representar, a nível de código, essa estrutura de lista de vários emails para um único usuário, você utilizaria o código 1 ou o código 2 ?
O que faz mais sentido para você ?
Bom, se você acha que o código 1 faz mais sentido para você, talvez você deva continuar com bancos de dados relacionais e a DSL SQL, porém, se a
nível de código, você achar que faz mais sentido utilizar o código 2, seja bem vindo ao banco de dados não relacional e ao MongoDB.
MongoDB é um sistema de armazenamento orientado a coleções e documentos:
{
"nome" : "João Neto",
"idade" : 28,
"email" : "email@exemplo.com"
};
Acima, temos um documento simples, com apenas 3 propriedades: nome, idade e email, uma coleção é uma lista desse documento.
http://forum.imasters.com.br/public/style_emoticons/default/seta.gif "O que significa Schemaless ?"
O grande ponto do MongoDB (e outros bancos de dados não relacional) é que ele é Schemaless.
"Schemaless ???"
Sim, ele não possui a rigidez pré-imposta por um schema, ou seja, eu posso ter uma nova propriedade para um novo documento a qualquer momento,
por exemplo:
{
"usuario" : "João Batista Neto",
"email" : "neto@exemplo.com",
"idade" : 28,
"contatos" : [
{
"nome" : "Fulano",
"email": "fulano@exemplo.com",
"gtalk": "fulano@gmail.com"
},
{
"nome" : "Beltrano",
"email": "beltrano@test.com",
"msn" : "msn@hotmail.com"
},
]
};
Perceba que Fulano possui um GTalk e que o Beltrano possui um MSN; Se estivéssemos trabalhando com um banco de dados com schema teríamos,
necessariamente, que prever essa situação, seja criando uma tabela de atributos ou uma nova coluna na tabela de contatos.
De fato, não tem nenhum sentido, termos um schema para o banco de dados, afinal, modelamos nossa aplicação e as regras já existem a nível de
aplicação; Se você não conseguiu engolir isso, pense na sua aplicação como sendo o schema do banco de dados, você define as regras à nível de
aplicação e guarda o que quiser no banco de dados afinal, não tem qualquer sentido ter uma redundância de regras.
"E essa estrutura desse código acima, o que é isso ? Parece Javascript !!"
Pois, não apenas parece Javascript; O código acima, utilizado para ilustrar a situação é Javascript Object Notation ou JSON.
De fato, o próprio terminal do Mongo é um interpretador Javascript:
[neto@localhost ~]$ /opt/mongodb/bin/mongo
MongoDB shell version: 1.6.1
connecting to: test
> test = {
... "usuario" : "João Batista Neto",
... "email" : "neto@exemplo.com",
... "idade" : 28,
... "contatos" : [
... {
... "nome" : "Fulano",
... "email": "fulano@exemplo.com",
... "gtalk": "fulano@gmail.com"
... },
... {
... "nome" : "Beltrano",
... "email": "beltrano@test.com",
... "msn" : "msn@hotmail.com"
... },
... ]
... };
{
"usuario" : "João Batista Neto",
"email" : "neto@exemplo.com",
"idade" : 28,
"contatos" : [
{
"nome" : "Fulano",
"email" : "fulano@exemplo.com",
"gtalk" : "fulano@gmail.com"
},
{
"nome" : "Beltrano",
"email" : "beltrano@test.com",
"msn" : "msn@hotmail.com"
}
]
}
>
E, para você que gosta de Javascript, procure brincar um pouco no terminal do Mongo, coisas interessantes como o código abaixo lhe farão se sentir
mais a vontade:
> fatorial = function( X ){
... if ( X <= 2 ) return X;
... else return X * fatorial( X - 1 );
... }
function (X) {
if (X <= 2) {
return X;
} else {
return X * fatorial(X - 1);
}
}
> fatorial( 5 );
120
Ou:
> Binet = function( X ){
... var sqrt5 = Math.sqrt( 5 );
...
... return ( 1 / sqrt5 ) * ( Math.pow( ( 1 + sqrt5 ) / 2 , X ) - Math.pow( ( 1 - sqrt5 ) / 2 , X ) );
... }
function (X) {
var sqrt5 = Math.sqrt(5);
return 1 / sqrt5 * (Math.pow((1 + sqrt5) / 2, X) - Math.pow((1 - sqrt5) / 2, X));
}
> Fibonacci = function( total ){
... var ret = [];
...
... for ( var i = 0 ; i < 10 ; ++i ) ret.push( Math.round( Binet( i ) ) );
...
... return ret;
... }
function (total) {
var ret = [];
for (var i = 0; i < 10; ++i) {
ret.push(Math.round(Binet(i)));
}
return ret;
}
> Fibonacci( 10 );
[ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
>
Temos usuários que podem ser administradores, analistas ou clientes porém, independente do grupo que esse usuário está, ele ainda é um usuário.
Vejamos:
user = {
name : "neto",
pswd : "neto",
real : "João Neto",
email : "email@teste.com",
groups : [ { name : "analista" } ]
}
Como pode ver, o usuário João Neto é um analista mas, e se tivermos algum usuário que, além de analista é também um "administrador" ??
Simples:
user = {
name : "douglas",
pswd : "douglas",
real : "Douglas",
email : "email@exemplo.com",
groups : [ { name : "administrador" } , { name : "analista" } ]
}
Percebe que a informação está contextualizada ?
Não precisamos de uma coleção apenas para armazenar esses grupos, eles estão no mesmo contexto dos usuários.
Para localizar os administradores do sistema é bem simples:
> db.users.find( { "groups.name" : "administrador" } );
{ "_id" : ObjectId("4c72b364a07f1b3129263edb"), "name" : "douglas", "pswd" : "douglas", "real" : "Douglas",
"email" : "email@exemplo.com", "groups" : [ { "name" : "administrador" }, { "name" : "analista" } ] }
Ou os analistas:
> db.users.find( { "groups.name" : "analista" } );
{ "_id" : ObjectId("4c72b356a07f1b3129263eda"), "name" : "neto", "pswd" : "neto", "real" : "João Neto", "email"
: "email@teste.com", "groups" : [ { "name" : "analista" } ] }
{ "_id" : ObjectId("4c72b364a07f1b3129263edb"), "name" : "douglas", "pswd" : "douglas", "real" : "Douglas",
"email" : "email@exemplo.com", "groups" : [ { "name" : "administrador" }, { "name" : "analista" } ] }
Perceba que a busca é feita usando a propriedade "groups.name", afinal, queremos localizar todos os documentos que possuam um grupo com
propriedade "name" igual a alguma coisa.
Se eu tiver conseguido ser claro, dê um toque, que vamos para um exemplo mais avançado, trabalhando com 2 contextos diferentes.
Mas como eu faço para ter uma listagem de todos os grupos existentes? Ou seja eu preciso ler todos os registros do banco para ter essa
informação?
Você utiliza MapReduce:
Map
> map = function(){
... this.groups.forEach( function( group ){
... emit( group.name , { count : 1 } );
... }
... );
... }
function () {
this.groups.forEach(function (group) {emit(group.name, {count:1});});
}
Reduce
> reduce = function( k , v ){
... for ( var i = 0 , t = v.length , r = 0 ; i < t ; ++i ){
... r += v[ i ].count;
... }
...
... return { count : r };
... }
function (k, v) {
for (var i = 0, t = v.length, r = 0; i < t; ++i) {
r += v[i].count;
}
return {count:r};
}
Executando:
> db[ db.users.mapReduce( map , reduce ).result ].find();
{ "_id" : "administrador", "value" : { "count" : 1 } }
{ "_id" : "analista", "value" : { "count" : 2 } }
Opz, temos 2 grupos:
administrador, em apenas 1 usuário
analista, em 2 usuários.
Um MAP nada mais é que um par CHAVE-VALOR, onde para a chave, usamos o nome do grupo.
Vamos supor que tenhamos uma lista (coleção) de usuários, esses usuários terão uma flag que indica qual o seu nível.
Os grupos não apenas são pré-definidos, pelo que pude entender, eles são rígidos.
Precisamos, logo de início, lembrar que por não ter um schema, a responsabilidade das regras dos dados ficam na aplicação.
Como eu modelaria, na aplicação, a situação acima:
Como pode ver, pelo fato de ser uma informação não mutável, rígida e aplicação depender desses literais, o grupo de usuários é, na verdade, uma enumeração.
Sendo assim:
UserGroup.php
<?php
interface UserGroup {
const ADMINISTRATOR = 1;
const ANALYST = 2;
const CLIENT = 4;
}
User.php
<?php
class User {
private $_id;
private $group;
private $name;
private $pswd;
/**
* @var MongoCollection
*/
private $users;
public function __construct( MongoCollection $userCollection , $name , $group , $pswd , MongoId $_id = null ){
$this->users = $userCollection;
$this->_id = $_id;
$this->group = $group;
$this->name = $name;
$this->pswd = $pswd;
}
if ( is_null( $userArr ) ){
throw new Exception( 'Opz, Dados inválidos.' );
} elseif ( isset( $userArr[ 'name' ] ) && isset( $userArr[ 'password' ] ) && isset( $userArr[ 'group' ]
)){
$userObj = (object) $userArr;
switch ( $user->getGroup() ){
case UserGroup::ADMINISTRATOR : echo 'Bem vindo, administrador...'; break;
case UserGroup::ANALYST : echo 'Bem vindo, analista...'; break;
case UserGroup::CLIENT : echo 'Bem vindo, cliente...'; break;
default:
echo 'Opz, não conheço esse grupo.';
}
} catch ( MongoException $m ){
echo 'Opz, problemas com o banco...';
} catch ( Exception $e ){
echo $e->getMessage();
}
A saída deverá ser:
Citar
A responsabilidade de saber se uma informação é uma data é da aplicação, pense que a aplicação é o schema de alto nível.
Dessa forma,
> use meudb;
> db.resources.save( { name : 'some resource' , since : new Date() } );
> db.resources.save( { name : 'other resource' , since : new Date() } );
> db.resources.find();
{ "_id" : ObjectId("4c7529027f8b9ac516000000"), "name" : "some resource", "since" : "Wed Aug 25 2010 11:30:26
GMT-0300 (BRT)" }
{ "_id" : ObjectId("4c752962873bc4c79b4785b0"), "name" : "other resource", "since" : "Wed Aug 25 2010 11:32:02
GMT-0300 (BRT)" }
Ou, usando o PHP:
<?php
$mongo = new Mongo( 'mongodb://meuUsuario:minhaSenha@meuHost:27017' );
$mongo->selectDB( 'meudb' )->selectCollection( 'resources' )->save( array( 'name' => 'some resource' , 'since'
=> new MongoDate() ) );
Dessa forma, você que terá a responsabilidade de saber o que está gravando e recuperando do banco de dados.
empre que você se deparar com uma situação de tratamento de dados, pergunte para a aplicação:
"De quem é a responsabilidade de fazer isso ?"
Se sua aplicação responder para você que a responsabilidade é do banco de dados, basta fazer:
> Date.prototype.sayHello = function(){
<econds() ].join( ':' ) , 'do dia' , [ this.getDate() , this.getMonth() + 1 , this.getFullYear() ].join( '/' )
].join( ' ' );
... }
function () {
return ["Olá, agora são", [this.getHours(), this.getMinutes(), this.getSeconds()].join(":"), "do dia",
[this.getDate(), this.getMonth() + 1, this.getFullYear()].join("/")].join(" ");
}
>
> resource = db.resources.findOne( { since : { $gte : new Date( 2010 , 7 , 24 ) } } );
{
"_id" : ObjectId("4c7529027f8b9ac516000000"),
"name" : "some resource",
"since" : "Wed Aug 25 2010 11:30:26 GMT-0300 (BRT)"
}
> resource.since
"Wed Aug 25 2010 11:30:26 GMT-0300 (BRT)"
> resource.since.sayHello();
Olá, agora são 11:30:26 do dia 25/8/2010
Mas vamos pensar em categoria/produto. Eu teria que listar as 300 categorias manualmente na aplicação? Caso seja isso, não seria pior assim?
Nesse caso que você citou ou mesmo no caso do grupo de usuários, como é aqui no fórum, que podemos criar e remover grupos dependendo da
situação.
Sempre que for necessário existir uma coleção para uma informação não estática, você pode criar referências:
Apenas mantendo a situação de Usuarios/Grupos para ficarmos no contexto do tópico:
> db.groups.save( { gid : 1 , name : 'Administrator' } );
> db.groups.save( { gid : 2 , name : 'Analyst' } );
> db.groups.save( { gid : 4 , name : 'Client' } );
Como pode ver, criamos 3 grupos de usuários, agora, vamos pegar o grupo Administrador:
> administrator = db.groups.findOne( { gid : 1 } );
{
"_id" : ObjectId("4c754d81061f2304f8a60c0a"),
"gid" : 1,
"name" : "Administrator"
}
Com a variável administrador criada, vamos criar um usuário:
> db.users.save( {
... uid : 1,
... name : 'João Batista Neto',
... group : new DBRef( 'groups' , administrator._id ),
... password : 'minhasenha'
... } );
Como pode ver, utilizei um DBRef para criar a referência, agora vamos ver como ficou nosso usuário:
> neto = db.users.findOne( { uid : 1 } );
{
"_id" : ObjectId("4c754fc2061f2304f8a60c10"),
"uid" : 1,
"name" : "João Batista Neto",
"group" : {
"$ref" : "groups",
"$id" : ObjectId("4c754d81061f2304f8a60c0a")
},
"password" : "minhasenha"
}
Perceba que a propriedade group possui agora duas propriedades, $ref que indica a coleção de onde estamos usando a referência e $id que é a
nossa "chave primária".
Se quisermos ver qual o nome do grupo que o usuário neto está, basta dar um fetch() na propriedade group:
> neto.group;
{ "$ref" : "groups", "$id" : ObjectId("4c754d81061f2304f8a60c0a") }
> neto.group.fetch();
{
"_id" : ObjectId("4c754d81061f2304f8a60c0a"),
"gid" : 1,
"name" : "Administrator"
}
> neto.group.fetch().name;
Administrator
"E se eu precisar mudar o nome do grupo de Administrator para Gestor ?"
Simples:
> administrator = db.groups.findOne( { gid : 1 } );
{
"_id" : ObjectId("4c754d81061f2304f8a60c0a"),
"gid" : 1,
"name" : "Administrator"
}
> administrator.name = 'Gestor';
Gestor
> db.groups.update( { gid : 1 } , administrator );
> db.groups.find();
{ "_id" : ObjectId("4c754d81061f2304f8a60c0a"), "gid" : 1, "name" : "Gestor" }
{ "_id" : ObjectId("4c754d8c061f2304f8a60c0b"), "gid" : 2, "name" : "Analyst" }
{ "_id" : ObjectId("4c754daf061f2304f8a60c0c"), "gid" : 4, "name" : "Client" }
Agora, o resultado:
> neto = db.users.findOne( { uid : 1 } ).group.fetch();
{ "_id" : ObjectId("4c754d81061f2304f8a60c0a"), "gid" : 1, "name" : "Gestor" }
> neto = db.users.findOne( { uid : 1 } ).group.fetch().name;
Gestor
Como pode ver, quando atualizamos alguma propriedade da referência, todos que a utilizam passam a ter o novo valor.
;)