O módulo ObjectDB é um framework de ORM (Object-Relational Mapper) que implementa o conceito Active Record. Esse framework foi escrito pelo Viacheslav Tykhanovskyi(vti) e, segundo sua própria descrição, é um framework “mapeador” objeto-relacional leve, livre de dependências (exceto o DBI) e flexível.
Neste artigo, pretendo abordar o uso desse framework, assim como a minha opinião pessoal sobre o uso e os cenários em que podemos aplicá-lo em vez de outros módulos muito mais conhecidos pela comunidade, como DBIC e Class::DBI.
Breve explicação
O ObjectDB é um módulo muito simples de se usar e não possui (ainda) ferramentas para geração das classes (modelos) automaticamente, o que faz com que você tenha que fazer a criação de classe a classe na unha. Mas isso não é um problema, uma vez que a sintaxe é muito simples e segue sempre a mesma conhecida e simples receita.
Infelizmente, o módulo ainda não possui nenhum cookbook, documentação ou uma documentação muito bem detalhada sobre como trabalhar com ele, então, estou escrevendo este documento como forma de apoio a alguns desenvolvedores que possam se interessar pelo uso desse módulo em algum projeto.
As classes de modelo
Bom, neste passo, vou abordar como iniciar a criação do mapeamento das tabelas propriamente ditas e, para tanto, vamos iniciar com uma classe base que manterá nossa conexão e que será herdada por todas as outras.
Base.pm
package Model::Base; use strict; use warnings; use base 'ObjectDB'; use DBI; our $dbh; sub init_db { my $self = shift; return $dbh if $dbh; $dbh = DBI->connect('DBI:SQLite:dbname=database.db', undef, undef); die $DBI::errorstr unless $dbh; return $dbh; } 1;
Note que o database handler é uma instância DBI mesmo, então não há nenhuma novidade até aqui se você está habituado ou já efetuou a conexão com o banco via DBI.
Podemos dizer que isso resume nossa classe que servirá como “a classe de base”.
Agora, vamos começar o mapeamento das tabelas… Imaginemos a necessidade do mapeamento de uma tabela de usuários, vamos ver como ficaria nossa classe.
User.pm
package Model::User; use base 'Model::Base'; __PACKAGE__->schema( table => 'user', columns => [qw/id name email password created/], primary_keys => ['id'], auto_increment => 'id', ); 1;
Agora vamos comentar um pouco o que vimos acima.
Note que logo no topo já utilizamos o use base para herdar a nossa classe Base.pm criada acima que detém o nosso database handler.
Abaixo, nós especificamos o nome da nossa tabela, o nome das colunas que serão utilizadas, a definição da coluna que é chave primária e declaramos que a mesma é um campo autoincrementado.
Fazendo isso e se os parâmetros de conexão na classe base estiverem corretos, já podemos começar a utilizar nossa classe de modelo para trabalhar com a tabela.
Create
O método create permite que as informações inseridas nas propriedades da classe sejam persistidas no banco de dados.
use Model::User; my $user = Model::User->new( name => 'Daniel Vinciguerra', email => 'dvinci@cpan.org', password => 'some hashed', ); $user->create;
…ou também
my $user = Model::User->new; $user->column('name', 'Daniel Vinciguerra'); $user->column('email', 'dvinci@cpan.org'); $user->column('password', 'some hashed'); $user->create;
Isso fará com que os registros sejam inseridos no banco de dados e retorna o “id” do registro inserido, por exemplo, para ser usado em algum outro cenário.
O create, fazendo uma analogia com o SQL, corresponde exclusivamente ao INSERT e nesse framework não faz o papel múltiplo de criar ou atualizar um registro caso ele já exista, como acontece em alguns outros frameworks.
Load
Este método carrega um registro no banco de dados retornando um único registro.
my $user = Model::User->new( id => 1 ); $user->load; say $user->column('name');
O trecho acima carregará o registro cujo id seja igual a 1.
Update
O método update é o responsável por persistir um registro que já existe no banco de dados.
use Model::User; my $user = Model::User->new( id => 1 ); $user->load; $user->column('name', 'Joe Doe'); ... $user->update;
Note que no exemplo acima nós tínhamos um objeto existente no banco de dados, alteramos o registro de nome e então executamos o método update que persiste as alterações realizadas no banco de dados.
Delete
Neste caso vamos imaginar uma necessidade de excluir um registro. my $user = Model::User->new( id => 1 ); $user->delete;
e isso remove o registro com o id igual a 1, mas e se eu quiser remover algum registro através de um outro ou outros campos?
my $user = Model::User->new; $user->delete( where => [name => 'Joe Doe'] );
Find
Agora teremos o retorno de uma lista com ou sem parâmetros.
my $user = Model::User->new; my $list = $user->find(); say $_->column('name') for @$list;
Isso deve retornar a lista de users, e quando precisarmos inserir uma condição na busca, por exemplo.
my $user = Model::User->new; my $list = $user->find( where => [id => 1, name => 'Joe Doe']); say $_->column('name') for @$list;
…como pode ver acima, usamos um parâmetro “where” para buscar resultados específicos.
Executando queries SQL
A execução de queries SQL também pode ser beneficiada com o uso do framework, uma vez que todas as classes que o utilizam em nossa camada de modelo herdaram o DBI…
my $dbh = Model::User->init_db; my $sth = $dbh->prepare("SELECT * FROM user"); $sth->execute;
Como a configuração já está definida na classe base, tudo que temos de fazer é chamar o método estático init_db e ganhamos como retorno o database handler.
Iterador
Mas a execução da query também pode ser ainda mais beneficiada com a possibilidade de uso de iteradores.
my $i = ObjectDB::Iterator->new( object => Model::User->new, sth => $sth );
…agora temos um iterador de Model::User e podemos percorrer o resultado da seguinte forma:
while (my $value = $i->next) { ... }
Relacionamentos
O relacionamento entre entidades de uma camada de modelo deve ser algo natural para um framework ORM, já que isso é uma característica muito comum em se tratando de bancos de dados relacionais.
Assim, o ObjectDB implementa isso de forma simples para que possamos descrever esses relacionamentos em nossas entidades.
A declaração de relacionamento segue em geral esta base:
__PACKAGE__->schema( ... relationships => { [alias_relacionamento] => { type => '[tipo_de_relacionamento]', class => '[classe]', map => { [coluna_referencia] => '[campo_extrangeiro]'} } } )
alias
Este item define o nome da entidade que será associada pelo relacionamento e será utilizado nas operações com a entidade relacionada.
package Model::Album; ... __PACKAGE__->schema( ... relationships => { music => { ... } } );
E usando nosso alias, definido neste exemplo como music, podemos efetuar as operações na entidade relacionada.
tipo de relacionamento
O tipo de relacionamento define qual a modalidade de “interação” que as entidades terão. Esses tipos de relacionamento se dividem em ‘one to one’, ‘one to many’, ‘many to one’ e ‘many to many’.
__PACKAGE__->schema( ... relationships => { music => { type => 'one to many', ... } } )
classe
É a classe da entidade que será relacionada com a classe corrente.
__PACKAGE__->schema( ... relationships => { music => { ... class => 'Model::Music', ... } } );
mapeamento
É onde definimos através de quais colunas as entidades se relacionarão.
__PACKAGE__->schema( ... relationships => { music => { ... map => { id => 'album_id' } } } );
Agora que já vimos como podemos declarar os relacionamentos entre as entidades, podemos manipular essas entidades relacionadas para criar, atualizar, excluir, contar etc.
# criando novo my $album = Model::Album->new( name => 'The Dark Side of the Moon', music => [ { name => 'Speak to Me', time => '01:30' }, { name => 'Breathe', time => '02:43' }, ... ] )->create; # contando $album->count_related('music'); # excluindo $album->delete_related('music'); $_->delete for @{$album->related('music')}; # pesquisando my $album = Model::Album->new( id => 1)->load; say $_->column('name') for @{$album->related('music')};
Extras
Aqui vamos abordar algumas features que achei interessante o módulo possuir, apesar de não ser uma implementação tão grande e difundida.
to_hash
Retorna o seu objeto como um hash para, por exemplo, convertê-lo em um objeto JSON mais facilmente.
my $user = Model::User->new( ... )->load; $user->to_hash;
transactions
tem a possibilidade de tratar transações.
Model::User->begin_work; ... # do what the fucker you want if( ... ) { Model::User->commit; } else { Model::User->rollback; }
error
Os erros são “guardados” em uma propriedade da classe para facilitar a análise e a exibição para o usuário.
my $user = Model::User->new( ... )->create; if ($user->error){ ... }
Por que devo usar o ObjectDB?
Sou adepto da análise e do uso da melhor solução no determinado cenário, e como esse é um framework que não necessita de dependências, vejo que o melhor cenário para uso dele é, por exemplo, em um ambiente onde nós desenvolvedores não tenhamos controle dos módulos que podem ou não ser instalados.
Um exemplo disso é quando estamos atuando em um projeto que fará uso de uma hospedagem compartilhada e nós, fatalmente, não conseguimos usar o CPAN para instalar módulos e/ou fica muito trabalhoso o deploy de módulos com muitas dependências. Vejo que nesse caso o uso de um framework sem dependências e em Pure Perl pode ajudar no desenvolvimento.
Considerações finais
Infelizmente, o ObjectDB ainda é um modulo novo mas muito promissor. O autor desse módulo vem desenvolvendo um grande trabalho na comunidade Perl e oferecendo módulos muito úteis, o que me leva a crer que ele será igualmente benéfico e muito promissor.
Como o trabalho ainda está “no início”, o módulo não possui nenhuma documentação rica, manual, cookbook, material de apoio etc., mas, como todo bom Perl Monk, isso pode ser facilmente sanado através de uma boa olhada e estudo dos testes do módulo, que são bem esclarecedores; e, caso a curiosidade sobre uma feature bater, basta dar uma olhada no source da implementação.
Links e referências
- ObjectDB at CPAN http://search.cpan.org/~vti/ObjectDB-0.990103/lib/ObjectDB.pm
- ObjectDB at Github (current version) https://github.com/vti/object-db
***
Artigo de Daniel Vinciguerra, publicado no Equinócio 2013 do grupo São Paulo Perl Mongers – http://sao-paulo.pm.org/equinocio/2013/mar/10-trabalhando-com-objectdb
Texto sob Creative Commons – Atribuição – Partilha nos Mesmos Termos 3.0 Não Adaptada, mais informações em http://creativecommons.org/licenses/by-sa/3.0/