21 de outubro de 2011

Reusing frequently used queries and joins

In DAL chapter on Web2py book, there's a section called  "Query, Sets, Rows".

It explains the use of the Query object in a very short way and doesn't explore its power.

Let's see some examples to optimize your application, shorten your code and don't repeat yourself.

Frequently used queries (i.e, common filtering by date):
db.define_table('show',
    Field('name', 'string'),
    Field('seen_on', 'datetime'))

rows1 = db((db.show.seen_on.month()==my_month) & \
            (db.show.seen_on.year()==my_year)) \
            .select(orderby='name')
print rows1

Now, if you want do the same query, plus filtering by name, too:
rows2 = db((db.show.seen_on.month()==my_month) & \
            (db.show.seen_on.year()==my_year) &\
            (db.show.name.like('Mary %')) \
            .select(orderby='name')
print rows2

See, date filtering part is duplicated. You can organize it in a better way, making your code like this:
db.define_table('show',
    Field('name', 'string'),
    Field('seen_on', 'datetime'))

db.show.my_filter = lambda m, y: db.show.seen_on.month()==m & \
                                  db.show.seen_on.year()==y

rows1 = db((db.show.my_filter(my_month, my_year)) \
            .select(orderby='name')
print rows1
 
rows2 = db((db.show.my_filter(my_month, my_year) & \
            (db.show.name.like('Mary %')) \
            .select(orderby='name')
print rows2

"Appending" personal filters to your table objects, you put them in a context and they goes everywhere with all other table properties and methods, throughout  your application.

You should make the same with joins. ;-)

Naturally this example doesn't show all benefits, but imagine some common cases:
a) Frequently used queries among your application: now they are standardized;
b) Complex and long queries: now they don't mess your code anymore. And are better readable;
c) Joins: now they are optimized and standardized.

As bigger your application and your team gets, better are the benefits.

What additional suggestion do you have to us? Tell us below.

Reusar queries usadas com frequência

No capítulo sobre DAL do livro de Web2py, existe uma seção chamada "Query, Sets, Rows".

Ela explica como usar o objeto Query de uma forma bem resumida e não explora todo o poder que ele tem.

Vamos ver alguns exemplos de como otimizar sua aplicação, encurtar seu código e praticar o princípio DRY (don't repeat yourself).

Consultas, ou queries, usadas com frequência (p.ex, filtragem comum por datas):
db.define_table('show',
    Field('name', 'string'),
    Field('seen_on', 'datetime'))

rows1 = db((db.show.seen_on.month()==my_month) & \
            (db.show.seen_on.year()==my_year)) \
            .select(orderby='name')
print rows1

Agora, se você quiser fazer a mesma query, mas filtrar também por nome:
rows2 = db((db.show.seen_on.month()==my_month) & \
            (db.show.seen_on.year()==my_year) &\
            (db.show.name.like('Mary %')) \
            .select(orderby='name')
print rows2

Viu? A parte que filtra por data está duplicada. Organize isso de um jeito melhor fazendo assim:
db.define_table('show',
    Field('name', 'string'),
    Field('seen_on', 'datetime'))

db.show.my_filter = lambda m, y: db.show.seen_on.month()==m & \
                                  db.show.seen_on.year()==y

rows1 = db((db.show.my_filter(my_month, my_year)) \
            .select(orderby='name')
print rows1
 
rows2 = db((db.show.my_filter(my_month, my_year) & \
            (db.show.name.like('Mary %')) \
            .select(orderby='name')
print rows2

"Adicionando" os filtros aos seus objetos table, você os coloca num contexto e eles estarão disponíveis em qualquer lugar da sua aplicação, assim como os outros métodos e propriedades.

Aconselho que você faça o mesmo com seus joins.

É natural que esse pequeno exemplo não mostre todos os benefícios dessa técnica, mas pense em alguns casos comuns:
a) Queries usadas com frequência: ficam padronizadas;
b) Queries longas e complexas: não deixam mais seu código bagunçado, e tornam-se mais fáceis de ler;
c) Joins: ficam otimizados e padronizados.

À medida que sua aplicação e sua equipe aumenta, você pode perceber melhor esses benefícios.

E você, tem alguma sugestão para melhorar? Colabore nos comentários.

30 de abril de 2011

Total control over your models

Every single web2py application that uses database, has its own models dir.

This directory is particularly important due to some characteristics:
  1. models directory files ares executed in alphabetical order;
  2. Your application's database tables definitions stay in it;
  3. Objects defined by these files become available on your app's global scope. They can be accessed by controllers and views.
I warn you to pay attention to the first one: files are executed in alphabetical order.

Some newbies have dificulties with that, and tend to think this is strange. I did it, until I concluded this is the most intuitive way to things work. It gives me freedom to organize my app.

However, to avoid some mistakes this way can bring to your app, we need to adopt a clear and fool proof convetion to name models files.

I use this simple and efficient structure:
  1. models/0_db.py
  2. models/1_menu.py
  3. models/5_models.py
  4. models/5_validators.py
I rename the automatically created db.py file to 0_db.py. So, I guarantee all basic definitions are executed before anything else. The DAL object, who provides all data access funcionalities, is created in this script.

I also rename menu.py to 1_menu.py forcing all menu definitions to be executed after DAL object instantiation.

With these modified names, I'm sure that all the code I'll develop will be executed after the scaffolding app web2py generated to me.

From that point I write models/5_models.py with my table definitions. Nothing but db.define_table() commands.

Validators are written in models/5_validators.py. But, why separated from model definitions? First, because web2py recommends you don't mix validators with  db.define_table(). Second, because at this point I'm certain all tables are already created and all cross references between them will work with no need to adjust definition sequences. Sometimes it messes my scripts.

When I have many tables, I can separate them in various scripts, i.e, 5_models_accounts.py, 5_models_finances.py, etc. And, the same way, their validators.

Here, your creativity can fly, but remeber some principles:
Explicit is better than implicit.
Simple is better than complex.
Flat is better than nested.
Readabilty counts.
In the face of ambiguity, refuse the temptation to guess.

27 de abril de 2011

Controle total sobre seus models

Toda aplicação web2py que usa banco de dados tem um diretório chamado models.

Esse diretório é particularmente importante devido a alguns fatores:
  1. Os arquivos do diretório models são executados em ordem alfabética;
  2. É ali que ficam as definições das tabelas dos bancos de dados de sua aplicação;
  3. Os objetos definidos por esses arquivos ficam disponíveis com escopo global na aplicação. Ou seja, podem ser acessados pelos controladores e pelas views.
Talvez a caracterísitica que mais exija atenção, é a primeira: execução em ordem alfabética.

Algumas pessoas iniciantes têm certa dificuldade com isso e acham meio estranho esse comportamento. Eu também achava, até concluir que esse é o jeito mais intuitivo para tudo funcionar.

Entretanto, para evitar alguns inconvenientes que esse funcionamento pode trazer, precisamos adotar uma nomenclatura clara e o mais simples possível para organizar nossos scripts dentro do diretório models.

Eu adoto a seguinte estrutura básica:
  1. models/0_db.py
  2. models/1_menu.py
  3. models/5_models.py
  4. models/5_validators.py
Em quê isso ajuda?

Eu renomeio o db.py gerado automaticamente pelo web2py para 0_db.py. Assim, garanto que todas as definições básicas são executadas antes de qualquer coisa. O objeto DAL, que fornece toda a funcionalidade de acesso a dados, é criado nesse script.

Também renomeio o menu.py para 1_menu.py fazendo com que as definições de menu sejam executadas depois da instanciação do objeto DAL.

Com esses nomes modificados, garanto que o esqueleto gerado automaticamente pelo web2py vai ser executado antes das partes que eu vou desenvolver depois.

A partir daí, escrevo o models/5_models.py com todas as definições de tabelas. Vale ressaltar que eu deixo dentro desse arquivo apenas os db.define_table().

Para escrever os validadores, eu crio o models/5_validators.py. Mas por que separado? Primeiro, porque o web2py recomenda que você não coloque os validadores dentro do db.define_table(). Segundo, porque nesse ponto eu tenho certeza de que todas as tabelas já foram definidas. Assim, garanto que qualquer referência cruzada entre elas funcione sem precisar alterar a ordem de criação delas. Às vezes isso desorganiza o script de criação das tabelas.

Se eu tiver muitas tabelas, nada me impede de dividir a definição delas em vários scripts, por módulos, por exemplo: 5_models_estoque.py, 5_models_financeiro.py, etc. E, da mesma forma, os validadores.

Aqui, sua criatividade é quem manda, mas lembre-se de alguns princípios:
Explícito é melhor do que implícito.
Simples é melhor do que complexo.
Linear é melhor do que encadeado.
A legibilidade conta.
Quando encontrar a ambiguidade, recuse a tentação de adivinhar.

20 de março de 2011

Sendo mais ágil com o appadmin

No artigo "Quem alterou um registro" eu mostrei como o web2py agiliza e padroniza a criação e controle de dados das tabelas automaticamente.

Mas quando estamos testando o aplicativo e usando o appadmin, precisamos de agilidade para incluir e alterar registros no bd.

Daí, para passar a validação de quem incluiu um registro, faça conforme abaixo:

""" Alterando o auth.signature para permitir
inserts anônimos pelo appadmin. """

db.minha_tabela.created_by.required = False
db.minha_tabela.created_by.requires = IS_EMPTY_OR( \
    db.carro.created_by.requires)

db.minha_tabela.modified_by.required = False
db.minha_tabela.modified_by.requires = IS_EMPTY_OR( \
    db.carro.modified_by.requires)

Assim, você não vai precisar escolher um autor para o registro.

Observações:
  1. Coloque o trecho de código mostrado, abaixo do db.define_table() da tabela que você está testando.
  2. Só use esse recurso em ambiente de desenvolvimento porquê você terá registros sem a identificação de quem os incluiu/alterou.

Fica a dica.

8 de fevereiro de 2011

Quem alterou um registro

Detalhes fazem a diferença, certo? O web2py se preocupa com eles para dar produtividade ao desenvolvedor.

Uma boa ideia é o auth.signature

Vamos a um exemplo:
db.define_table('cadastro',
  Field('nome', 'string', length=100),
  Field('telefone', 'string', length=20),
  auth.signature)

Esse comando vai criar uma tabela chamada cadastro com os campos id (criado automaticamente), nome e telefone, além de: created_on, created_by, modified_onmodified_by.

Isso faz com que possamos saber quando um registro foi incluído (created_on) ou alterado (modified_on).

O auth.signature funciona automaticamente integrado ao sistema de autenticação do web2py. Portanto, se um usuário logado incluir ou alterar o registro, o código de usuário dele ficará registrado nos campos created_by e modified_by.

Fica aí a dica.