Rails console em modo teste

Com o Bash é possível redefinir uma variável de ambiente durante o tempo de execução de um programa. Isso pode ser útil em diversos casos, e me foi útil hoje, para entrar no console do Rails e interagir com a base de testes, e não com a de desenvolvimento. Basta fazer, dentro do diretório raiz da sua aplicação:

$ RAILS_ENV=test ./script/console

Para quem não sabe, é a variável de ambiente $RAILS_ENV que indica ao Rails qual o ambiente atual (produção, desenvolvimento ou testes). Você pode consultar o seu valor atual dentro do próprio Rails console fazendo:

>> ENV['RAILS_ENV']

Pluralização do Rails fora do Rails

Estava eu hoje criando algumas visões Oracle quando precisei ver como o Rails “pluralizaria” o nome de uma tabela. Na mesma hora me deu preguiça de consultar o Rails Pluralizer do Geoffrey Grosenbach, que eu vinha usando. Decidi perder um pouco mais de tempo e descobrir como poderia acessar diretamente as bibliotecas do Rails responsáveis por “pluralizar” e “singularizar” os substantivos através do IRB.

Resumo da ópera: é só abrir o IRB e utilizar os métodos do módulo Inflector, presente na biblioteca ActiveSupport. Exemplos:

$ irb
>> require 'activesupport'
=> true
>> Inflector.pluralize 'datum'
=> "data"
>> Inflector.singularize 'data'
=> "datum"

Dentro do Rails console você não precisa carregar a ActiveSupport, pois ela é carregada durante a inicialização do console.

A documentação oficial desse módulo pode ser encontrada aqui. Prepare-se para ler muitas “railzices” como “tableize” e “titleize”, para citar algumas.


Modificando uma tabela Oracle para conversar com o Rails

Minha saga e dos meus colegas de projeto com o Oracle continua. Estamos tentando configurar um framework web (o Rails) para que ele interfira o mínimo possível na definição original das tabelas do nosso banco de dados. Ela é uma base Oracle com convenções próprias que, idealmente, não deveria ter seu esquema modificado de forma a agradar nenhum framework. Não deveria.

Mas eis que o Rails não suporta tabelas com chaves primárias compostas (e a extensão Composite Primary Keys, que resolveria o problema, atualmente não está funcionando). Para quem não tem muita experiência com banco de dados, vou ilustrar com um exemplo de uma tabela que possui uma chave primária composta de 2 colunas (e não 1, que seria o ideal para se trabalhar com o Rails).

Em uma instituição de ensino, por exemplo, poderíamos ter alunos e cursos (disciplinas) vinculados entre si por meio da seguinte tabela:

create table matricula (
    cd_estudante int REFERENCES estudante (cd_estudante),
    cd_curso int REFERENCES curso (cd_curso),
    nota number,
    CONSTRAINT matricula_pk PRIMARY KEY (cd_estudante, cd_curso)
);

Nossa chave primária é, portanto, composta. Para que o Rails consiga interagir com essa tabela é necessário que ela tenha 1 única chave primária (neste caso, uma chave artificial, que chamarei de cd_artificial). Os campos cd_estudante e cd_curso podem continuar formando uma dupla de campos únicos, com a adição de uma restrição de integridade. Nossa tabela “Rails friendly” deveria ter, portanto, a seguinte definição:

create table matricula (
    cd_artificial int,
    cd_estudante int REFERENCES estudante (cd_estudante),
    cd_curso int REFERENCES curso (cd_curso),
    nota number,
    CONSTRAINT matricula_pk UNIQUE (cd_estudante, cd_curso)
);

O problema do nosso projeto é que algumas das tabelas que precisam sofrer essa metamorfose já possuem milhões de registros (ou seja, definir uma nova tabela, migrar os dados da antiga para a nova e remover a antiga custa muito tempo).

Nossa única regalia é poder parar o sistema para realizar as modificações. O que segue abaixo, portanto, é um passo-a-passo de como transformar aquela primeira definição de matricula na segunda, garantindo que, após o procedimento:

  • Todos os registros tenham um cd_artificial populado corretamente
  • Qualquer código-fonte existente que manipule a tabela continue funcionando, pois não precisará saber da existência de cd_artifical
  • Novos registros possam ser inseridos tanto a partir da aplicação web, quanto a partir do sql*plus, sem que um valor para o campo cd_artificial precise ser definido explicitamente.

Para ilustrar melhor o exemplo, apresento agora a definição de todas as 3 tabelas do sistema fictício, e também os registros inseridos para possibilitar o teste do procedimento.

create table estudante (
    cd_estudante int primary key,
    nm_estudante char(30)
);

create table curso (
    cd_curso int primary key,
    nm_curso char(30)
);

create table matricula (
    cd_estudante int references estudante (cd_estudante),
    cd_curso int references curso (cd_curso),
    nota number,
    constraint matricula_pk primary key (cd_estudante, cd_curso)
);

insert into estudante values (1, 'Caio');

insert into curso values (1, 'AWK');
insert into curso values (2, 'PHP');
insert into curso values (3, 'Python');
insert into curso values (4, 'Ruby');

insert into matricula values (1, 1, 5);
insert into matricula values (1, 2, 9);

Observe as colunas e os registros da tabela matricula, para poder comparar com a versão final:

SQL> select * from matricula;

CD_ESTUDANTE   CD_CURSO       NOTA
------------ ---------- ----------
           1          1          5
           1          2          9

Lembre-se de que os passos a seguir se aplicam somente ao Oracle. Se você estiver utilizando MySQL o procedimento é muito mais simples, e não envolve a criação nem de uma seqüência e nem de um gatilho. Provavelmente no PostgreSQL as coisas devem ser mais simples, mas não tenho o conhecimento necessário para dizer com certeza.

Vamos aos passos.

Passo 1 - Remover a restrição de chave primária de cdestudante,cdcurso

Para que a nova chave primária possa ser definida, a antiga deve ser eliminada.

alter table matricula drop constraint matricula_pk;

Passo 2 - Definir a restrição unique para cdestudante,cdcurso

A nossa antiga chave primária garantia que um mesmo aluno não poderia ser vinculado ao mesmo curso mais de uma vez. Como a chave foi eliminada, essa restrição de integridade não existe mais. Precisamos defini-la novamente.

alter table matricula add constraint matricula_pk unique(cd_estudante,cd_curso);

Passo 3 - Adicionar a coluna cd_artificial na tabela

Agora podemos adicionar a coluna que se tornará nossa nova chave primária.

alter table matricula add cd_artificial numeric(10);

Note que, neste momento, a coluna cd_artificial não possui valores para nenhum dos registros de matricula:

CD_ESTUDANTE   CD_CURSO       NOTA CD_ARTIFICIAL
------------ ---------- ---------- -------------
           1          1          5
           1          2          9

Passo 4 - Popular a coluna cd_artificial

Precisamos, portanto, popular a coluna recém-criada. Neste caso queremos atribuir valores a partir de 1 até o número total de registros da tabela.

update matricula set cd_artificial = rownum;

Veja o resultado:

CD_ESTUDANTE   CD_CURSO       NOTA CD_ARTIFICIAL
------------ ---------- ---------- -------------
           1          1          5             1
           1          2          9             2

Passo 5 - Adicionar a restrição de chave primária à coluna cd_artificial

Agora que a nova coluna já possui dados, ela pode se tornar nossa chave primária.

alter table matricula add constraint matricula_pk primary key (cd_artificial);

Passo 6 - Definir a seqüência que populará o campo cd_artificial

Campos auto incrementais não existem nativamente no Oracle da forma como existem no MySQL. Eles precisam ser definidos com um pouco mais de esforço: deve-se criar uma seqüência e um gatilho.

A seqüência fica responsável por guardar, de forma transacional, um número atual, e gerar um próximo número quando solicitada. Neste caso, como nossa tabela já possui 2 registros, precisamos que a seqüência inicie em 3:

create sequence matricula_seq start with 3;

Passo 7 - Popular o campo cd_artificial automaticamente

O gatilho (trigger) será responsável por interceptar a inserção de um registro na tabela matricula. Todo registro terá seu campo cd_artificial definido como matricula_seq.nextval antes da inserção (before insert), sem que seja preciso defini-lo explicitamente.

create or replace trigger matricula_pk_trig
   before insert
   on matricula
   for each row
begin
   select matricula_seq.nextval
   into :new.cd_artificial
   from dual;
end;
/ -- Esta contra-barra marca o fim da declaração da trigger.

Na verdade, é possível forçar um valor diferente de matricula_seq.nextval para o novo registro, desde que esse novo valor não viole a restrição de integridade da chave primária.

Para testar o gatilho, vamos inserir um novo registro que não especifica cd_artificial:

SQL> insert into matricula (cd_estudante,cd_curso,nota) values (1,3,7);

SQL> select * from matricula;

CD_ESTUDANTE   CD_CURSO       NOTA CD_ARTIFICIAL
------------ ---------- ---------- -------------
           1          1          5             1
           1          2          9             2
           1          3          7             3

Pronto! Agora o Rails será capaz de manipular essa tabela sem problemas.


Sequence CurrVal no Oracle

Estou tentando construir uma aplicação Rails sobre uma base de dados Oracle já construída, com suas próprias convenções de nome, sem chaves artificiais para tabelas que fazem relacionamentos NxM… enfim, estou tendo um certo trabalho. A pior parte, no entanto, é que não sou aquilo que se pode chamar de “expert” em Oracle, muito pelo contrário. E foi aí que começaram meus problemas na tarde de hoje.

Eu estava tentando entender como o Rails gerenciava um campo “auto incremental” no Oracle. Para quem não sabe, esse conceito de “auto incremental” é recorrente no MySQL, onde existe uma instrução específica pra isso. No MySQL, você marca uma chave primária como um campo inteiro, auto incremental, e nunca mais se preocupa. Acontece que no Oracle não é bem assim. Você precisa simular esse comportamento em alguns passos.

O primeiro passo é criar uma tabela que possua como chave primária um campo de tipo inteiro. Por exemplo (todos os comandos SQL do artigo são executados diretamente na péssima interface interativa do Oracle, o sqlplus):

create table students (id int, name char(255));

O segundo passo é criar uma seqüência. O que realmente é uma seqüência não é muito importante neste momento: basta entender que ela guarda um valor corrente, e que é capaz de gerar seu próximo valor. O seguinte comando cria a seqüência que eu utilizei para popular o campo id de todos os registros da tabela student.

create sequence students_seq;

Para inserir um registro em students com o campo id definido por students_seq devemos utilizar a propriedade nextval da seqüência, que retorna o próximo valor da mesma e o define como seu valor atual. Veja o exemplo:

insert into students (id,name) values (students_seq.nextval, 'Caio');
> 1 registro criado.
select id from students;
> 1
select students_seq.currval from dual;
> 1
commit;

Ou seja, tudo funcionando como esperado. Para interagirmos com essa tabela a partir de uma aplicação Rails seria preciso apenas definir a seguinte classe em app/models/student.rb:

class Student < ActiveRecord::Base
end

Podemos agora interagir com nossa base Oracle a partir do console do Rails. Vejamos os seguintes comandos no console:

>> Student.find :all
=> [#<Student id: 1, name: "Caio                                               ...">]
>> joao = Student.new(:name => 'Joao')
=> #<Student id: nil, name: "Joao">
>> joao.save
=> true
>> joao.id
=> 2

Na primeira linha recuperamos todos os registros, e nos é retornado apenas um, que criamos anteriormente diretamente via Oracle. Na terceira linha definimos um novo objeto, e na quinta linha salvamos esse objeto no banco. Nesse momento, a consulta que o Rails executou foi, na prática, a seguinte:

insert into students (id,name) values (students_seq.nextval, 'Joao');

Ou seja, antes da inserção o valor corrente de students_seq avançou 1 unidade, tornando-se 2. Esse valor foi retornado para o comando insert, e Joao ganhou um id igual a 2.

Se agora abrirmos o console do Oracle, você esperaria que o valor corrente de students_seq fosse 2, certo? Mas veja o que o Oracle nos diz:

select students_seq.currval from dual;
> A seqüência students_seq.currval ainda não foi definida nesta sessão

De início não dei muita importância pra essa mensagem, pensei: “mas como, se o Rails atribuiu valor 2 pro último registro, essa consulta TEM que retornar 2!”. Mas a mensagem do Oracle diz tudo o que precisamos saber (embora de uma forma que lembra uma mensagem de erro genérica que você também gera para os seus sistemas quando está com preguiça).

O problema é que o currval de uma seqüência realmente depende da sessão onde estamos. Por sessão, neste caso, entende-se o contexto em que as consultas estão sendo executadas. Se eu abro um console do Oracle (sqlplus) crio uma sessão. Se o fecho e o abro de novo, acabei de gerar uma nova sessão.

Na sessão Oracle mantida pelo console do Rails, esse valor certamente é 2. Mas esse valor não é o mesmo nas outras sessões, acredito eu por conta do controle de concorrência. Pois se, enquanto eu escrevo este artigo, o nextval da seqüência foi solicitado 20 vezes por outras transações, é claro que currval não poderá continuar sendo 2. Além disso, calcular esse valor real não me parece trivial e nem muito útil - talvez por isso o Oracle exiba apenas o valor da sessão.

select students_seq.nextval from dual;
> 3

Como o Oracle me informou “3″, sei que nenhuma outra sessão manipulou essa seqüência nesse meio tempo. Para quem entende de Oracle isso certamente era óbvio, mas pra mim demorou um tempo pra cair a ficha.


Simples deploy de aplicações Rails - quando isso será possível?

O recente rant de Zed Shaw, o criador do Mongrel, está repercutindo muito pela comunidade Rails e Ruby. Zed conta como foi ignorado pelo Core Team do Rails e como foi desrespeitado por algumas empresas de software nos EUA e no Canadá. Ele também detona uma das principais consultorias Rails do mercado, a ThoughtWorks.

Um dos locais onde esse rant repercutiu foi no Ruby Inside. Em geral as pessoas têm dito que já viveram situações semelhantes àquelas que o Zed afirma ter vivido, mas dizem que isso não é nada de novo e que com outras comunidades (de outras linguagens e frameworks) isso não é diferente. Toda comunidade tem suas maçãs podres e devemos tentar nos afastar. Eu poderia destacar diversas passagens desse post, mas fico com uma que me pareceu a mais preocupante:

[…] Notice how it took me a few seconds to reply. This one single statement basically means that we all got duped. The main Rails application that DHH created required restarting ~400 times/day. That’s a production application that can’t stay up for more than 4 minutes on average.

Preciso dizer mais alguma coisa? Alguma coisa está muito errada. O DHH disse que sua aplicação não ficava mais de 4 minutos de pé em 2007.

Apenas para registrar, não concordo com a postura do Zed. Ele poderia ter dito tudo o que disse de outra forma, sem ser tão baixo com essas pessoas como ele afirma que elas o trataram.

Mas o que me levou a escrever este post foi o comentário, nesse mesmo post do Ruby Inside, de Peter Cooper, com o qual eu (sendo um programador PHP há alguns anos) concordo em gênero, número e grau. Ele diz:

One thing Zed’s rant also highlights is something I have mega issues over, and the reason I still use a lot of PHP code (such as WordPress) even though I can’t code PHP myself. It’s an absolute nightmare deploying Rails apps.

Alguém discorda? Aliás, alguém tem coragem de discordar? Isso não é uma opinião, é simplesmente um fato. Se você não tem a conta de root do servidor onde o deploy será feito é bem provável que você terá sérios problemas ao fazer deploy de sua aplicação Rails - e se você tiver a senha, então poderá encontrar diversos problemas relacionados a distribuição Linux desse servidor, pois nem todos rodam uma baseada em Debian (que, ao menos para mim, é a melhor opção hoje em dia para instalação e configuração de qualquer coisa relacionada a Ruby e Rails).

Mas o Paul Cooper continua:

I don’t get why some really clever so-and-so hasn’t come up with a mod_ruby that works in a somewhat similar fashion to mod_php. PHP as a language is no more “stable” than Ruby, but people perceive PHP as being more mature simply because you can upload a .php file and bam, it works. Not so with Ruby. You gotta know about daemons, proxying stuff around.. like.. WTF? If I didn’t have crap for brains, I’d probably start such a project, but I’m totally blown away that no-one is making moves in this direction already?

Preciso dizer mais alguma coisa? Todos falam mal do mod_ruby e parece que ninguém quer fazer nada a respeito, talvez porquê aproximaria o Ruby, ó perfeito Ruby, do tão odiado PHP. No fim das contas temos o mod_python, que até onde sei não tem grandes problemas, e o mod_php, que é simplesmente tão difundido entre os servidores Apache mundo afora que os programadores PHP, de fato, nunca precisaram pra pensar que o deploy de uma aplicação web poderia ser problemático.

Ao contrário do Paul Cooper, eu programo em PHP, e continuarei com ele enquanto o deploy de Ruby não for adequado. Mas eu sou otimista a esse respeito. Acho que o Ruby vai continuar crescendo, não apenas por conta do Rails, mas por todas as discussões que ele tem colocado na mesa (closures, bdd, dsl’s, metaprogramação etc.).


Importando dados default no Rails com YAML

Migrações são a forma “Rails” de resolver o problema da atualização de versões de uma aplicação web, uma vez colocada em produção. Cada migração em geral declara, em seu método up, a definição de uma tabela e a criação de registros default para a mesma. A criação desses registros pode ser feita com puro código Ruby, mas quando precisamos inserir mais de, digamos, 20 registros, pode ser mais interessante utilizar código YAML para fazer esse trabalho.

Considere a migração que cria uma tabela de categorias:

class CreateCategories < ActiveRecord::Migration
  def self.up
    create_table :categories do |t| 
      t.column 'name', :string, :null => false
    end 
  end 

  def self.down
    drop_table :categories
  end 
end

A tabela categories, de fato, não é muito interessante: possui apenas o campo name.

E aqui entra a técnica que justifica este artigo: em vez de inicializar os registros default como instâncias de um Model, é possível utilizar a classe Fixtures para importar esses dados de um arquivo YAML. Observe a migração abaixo:

require 'active_record/fixtures'

class LoadDefaultCategories < ActiveRecord::Migration
  def self.up
    Fixtures.create_fixtures("#{RAILS_ROOT}/db/migrate/defaults", 'categories')
  end 

  def self.down
    Categories.delete_all
  end 
end

Ok, o código ficou bem pequeno, parece interessante… mas o que essa migração faz? A primeira linha importa a classe Fixtures. Precisamos dela, obviamente, pois ela sabe lidar muito bem com arquivos YAML. A única linha do método up realiza duas tarefas:

  • Transforma os dados contidos no arquivo YAML db/migrate/defaults/categories.yml em instâncias do nosso modelo Category
  • Insere os dados (ActiveRecord::Base#save) na nossa base de desenvolvimento

O método Fixtures#create_fixtures recebe dois parâmetros importantes:

  • O primeiro é o caminho para o diretório de fixtures que se deseja importar. Sim, de fato não estamos criando fixtures (mais sobre isso no fim do artigo).
  • O segundo é o nome de um model (presente nesse diretório) que se deseja importar. 'categories', por exemplo, será correspondente ao arquivo categories.yml dentro daquele diretório. Esse arquivo deve seguir o padrão de definição de fixtures do Rails.

O arquivo db/migrate/defaults/categories.yml poderia ter o seguinte conteúdo:

ruby:
  id: 1
  name: 'ruby'

rails:
  id: 2
  name: 'rails'

vim:
  id: 3
  name: 'vim'

Não inventei essa técnica, mas perdi a referência onde a aprendi, infelizmente. Ela é uma pequena gambiarra dentro do Rails, evidentemente, pois utiliza uma classe (Fixtures) que tem por objetivo carregar dados YAML para dentro da base de testes durante a execução de unit testes ou specs. Como a técnica não apresentou problemas até agora, continuarei com ela até tropeçar em uma solução melhor.


Symfony: uma visão geral

O último framework que pretendo analisar este ano é o Symfony. Há algumas razões para escolhê-lo:

  1. É escrito em PHP (minha análise já inclui frameworks em Ruby e Python)
  2. Não tenta ser uma versão PHP do Rails (não que isso fosse algo ruim)
  3. Possui farta documentação (inclusive um livro em português, que divide sua atenção com outros frameworks PHP populares)
  4. Possui um livro oficial já publicado (isso é uma vitória quando consideramos que há em torno de 50 frameworks PHP por aí)

Este artigo, diferente de outro em que comparei Rails e Django frente a frente, pretende ser mais “light”: aponta apenas as diferenças de abordagens entre Rails e Symfony, sem entrar em tantos detalhes. Dito isso podemos seguir em frente.

Instalação

A instalação do framework foi feita sem grandes problemas, seguindo o tutorial do Symfony Brasil para instalação no Ubuntu. Basicamente fazemos a instalação com auxílio do PEAR. Não conheço nada a respeito do projeto PEAR, mas para mim pareceu uma alternativa PHP ao RubyGems.

Rodando a primeira aplicação

Durante a instalação somos “convidados” a criar a nossa primeira aplicação. Após a instalação fica disponível o comando symfony, responsável por criar a estrutura básica de diretórios do projeto, como acontece com o Rails. Em seguida, somos também convidados a configurar nosso apache2.conf para rodar essa nossa aplicação… peraí, já estamos no Apache? Será que não existe, dentro do Symfony, um servidor web para desenvolvimento, que iniciamos com um comando apenas, ao estilo WEBrick, do Ruby, ou o servidor de desenvolvimento do Django?

Resolvi ir direto à fonte, o livro do Symfony, no capítulo 3, “Running Symfony”. A seção “Configuring the Web Server” vai pelo mesmo caminho: configuração do Apache. Isso me intimidou um pouco, pois tenho um histórico de surras memoráveis contra o Apache (horas a fio para configurar as regras mais simples). Decidi não rodar minha aplicação recém-criada para evitar muito estresse.

Decidi, então, conhecer o framework “de fora para dentro”: como são implementadas suas funções principais, tais como ORM, validações e TDD.

Mapeamento Objeto-Relacional

Da mesma forma como Rails e Django, o Symfony adota um Mapeamento Objeto-Relacional (ORM) como maneira de facilitar a programação de código orientado a objetos sobre bancos de dados relacionais.

O ORM do Symfony é delegado a uma biblioteca provida por terceiros (Propel), a qual é desenvolvida sobre uma biblioteca de abstração de banco de dados (Creole). Propel parece ser uma biblioteca madura e robusta, de acordo com as funcionalidades descritas no site do projeto.

A parte que mais me interessa no caso de ORM é a definição de relacionamentos entre as tabelas ou modelos. A abordagem da ActiveRecord, por exemplo, fez uso das facilidades de Ruby para metaprogramação, criando uma Linguagem de Domínio Específico (DSL) para definição de modelos e suas relações. Como a Propel se sai nessa tarefa?

Tanto as tabelas Propel quanto os relacionamentos entre essas são definidos por esquemas XML. Bom, lembro de ter lido uma vez, não lembro onde, que XML só podia ser considerada uma linguagem “peso leve” se comparada à Java, uma linguagem “peso pesado”. Por exemplo, na comunidade Ruby, sempre que possível, vejos os projetos utilizando YAML em detrimento de XML. Por que? Pois YAML é mais fácil de manter manualmente, por não ser baseada na “sintaxe do abre-e-fecha” do XML. “Ah, mas quem precisa manter XML manualmente se temos IDE enormes que fazem isso por nós?”. Ok, cada um na sua.

E não é que a comunidade Symfony também gosta de YAML? Quando trabalhando em um projeto Symfony você define seus modelos em YAML, e o framework se encarrega de traduzir suas definições para o formato XML do Propel. Isso é muito bom!

Embora eu não tenha encontrado esse fato na minha leitura rápida pela documentação, Elliot Smith alerta para o fato de que no Propel é necessário declarar 4 classes por tabela, o que achei bastante estranho.

Validações

Uma das diferenças entre Rails e Symfony é o local onde são declaradas as validações dos modelos. No Rails elas são declaradas dentro dos próprios modelos, como código Ruby. Já no Symfony são declaradas externamente, em arquivos YAML.

Controle da evolução do banco de dados

O controle de evolução do banco de dados, no Rails, é feito por meio de migrações. Com elas é possível escrever, em Ruby, operações de atualização do seu banco de dados e garantir a atualização dos sistemas em produção de forma (teoricamente) indolor.

Como isso é feito no Symfony? De acordo com o wiki do projeto esse suporte existe, mas não conta com abstração de SGBD. Ou seja: migrações à base de SQL puro, sendo, portanto, dependente do SGBD escolhido.

Para maiores informações confira o artigo “Migrations” no wiki do Symfony.

Agilidade no desenvolvimento

Uma das coisas que torna ao menos o meu desenvolvimento ágil é o IRB do Rails. Com ele posso interagir com os meus modelos, testar minhas validações quando nada parece fazer sentido etc. Com o Django também é possível utilizar o shell interativo para interagir com os modelos. Mas e no Symfony, como fica? Acredito que não seja possível simular algo tão poderoso, depois de ter procurado sobre o assunto no Google. Isso é uma desvantagem grande.

Como não tive uma experiência real de desenvolvimento, recomendo que você confira o artigo do Elliot Smith sobre o desenvolvimento de uma pequena “blog engine” no Symfony.

Testes de software

O Symfony trabalha com um framework embutido para testes, chamado Lime. Eu não sou experiente em testes de software, e menos ainda com testes para código PHP. Mesmo assim foi possível compreender, pela documentação do capítulo 15 do livro do Symfony, que se trata de um framework completo: há testes para modelos, para controllers e para views, incluindo os testes com suporte a seletores CSS (que testam, por exemplo, se os elementos HTML que esperamos fazem parte do documento que retorna como resposta a uma determinada requisição HTTP).

Algo que chama a atenção é que, diferentemente dos frameworks xUnit, os testes do Lime não são cercados por uma classe. Os testes são executados no escopo global, após a inclusão (require) de alguns scripts. Acabei sem entender como o framework lida com a ausência dos métodos setup e tear_down. Para separar melhor a finalidade de cada teste o Lime usa e abusa da separação dos testes por diversos arquivos.

Links relacionados


Comparação entre Rails e Django

Recentemente decidi conhecer o framework Django pois, de acordo com algumas discussões na lista do Rails, aquele era mais adequado para o desenvolvimento de aplicações modulares. Como disse em outro post, estou construindo uma aplicação web extensível (através de plugins e temas, por exemplo).

Meus testes com o Django duraram mais ou menos uma semana. Em primeiro lugar eu precisava ganhar um pouco de conhecimento da linguagem sobre a qual o framework é construído: Python. Essa foi a parte mais fácil. Não porquê eu a tenha considerado uma linguagem simples de aprender, e sim porquê eu não precisava aprender tanta coisa assim antes de poder mergulhar no uso do framework.

Antes de começar a comparação, gostaria apenas de adiantar uma das conclusões: o tipo de modularidade que eu preciso, que é a capacidade de escrever uma aplicação com componentes plugáveis (semelhante aos temas, plugins e widgets do Wordpress) é bem diferente do conceito de modularidade pregado pelo Django (e isso eu só descobri ao longo do estudo). O significado de modularidade para os desenvolvedores do Django é a facilidade para trocar componentes completos entre aplicações. Isso ficará mais claro ao longo deste artigo.

Este artigo assume que você tem um entendimento superficial sobre a proposta do Rails, mas não do Django. As versão analisadas são Rails 1.2 e Django 0.96.

A terminologia dos frameworks

As diferenças entre Rails e Django começam cedo, já na terminologia. Ambos são construídos sobre a arquitetura MVC. Mas o que cada uma dessas letras significa em cada uma das abordagens?

O que há de comum nas terminologias:

  • O M significa Model, modelo, e se traduz na biblioteca de software que abstrai o banco de dados da aplicação. Classes são mapeadas para tabelas, e objetos são mapeados para registros.

O que muda:

  • C (Controler)
    • Em Rails, o Controller é uma classe cujos métodos são responsáveis por atender as requisições da aplicação. Cada método, em geral, realiza funções tais como recuperar uma listagem de registros do banco de dados e deixar os dados prontos para que um View os apresente.
    • Os criadores do Django vêem o próprio framework como o Controller da aplicação, e entendem por View aquilo que o Rails entende por Controller.
  • V (View)
    • Em Rails, um View é um trecho de código responsável por gerar saída HTML. Nenhuma operação de acesso ao banco de dados deve ser realizada em um View (essas fazem parte do Controller). As variáveis inicializadas no Controller ficam disponíveis para o respectivo View.
    • O Django entende por Template aquilo que o Rails entende por View.

Resumindo, tem-se as seguintes relações:

  • Rails Controller X O próprio framework Django
  • Rails Model X Django Model
  • Rails View X Django Template

Criando uma aplicação

Em Rails uma aplicação pode ser criada com um simples comando:

> rails nome_da_aplicacao

Em Django:

> django-admin.py startproject nome_da_aplicacao

Estrutura de diretórios

Uma vez criada a aplicação, acabamos com a seguinte estrutura de diretórios para a aplicação Rails (apenas os principais arquivos foram listados):

app/
 controllers/
 helpers/
 models/
 views/
   templates/
config/
 routes.rb
 database.yml
db/
script/
 generate
test/
vendor/

Para o Django, a estrutura criada é bem mais enxuta:

__init__.py
manage.py
settings.py
urls.py

Enquanto o Rails cria uma grande árvore de arquivos, o Django cria apenas 4.

Durante a construção de uma aplicação Rails essa estrutura de diretórios, em geral, é mantida. O desenvolvedor faz uso do programa script/generate para gerar Controllers, Models e Views (e seus respectivos testes).

No Django, ao contrário, o desenvolvedor acabará, obviamente, com uma estrutura de diretórios bem maior do que aquela com a qual iniciou o projeto. Em geral cada nova funcionalidade demanda um novo diretório, pois essa é a filosofia do Django: cada diretório da sua aplicação deve, em teoria, ter a capacidade de ser “plugado” em uma outra aplicação Django e, com poucas configurações, continuar funcionando. É dessa forma que os desenvolvedores do Django entendem a palavra modularidade: uma aplicação é construída a partir de componentes menores, os quais podem ser trocados entre aplicações diferentes.

Processamento de requisições

Uma vez entendida a diferença de nomenclaturas entre os dois frameworks pode-se entender facilmente que, de forma geral, o processamento de requisições ocorre da mesma maneira nos dois frameworks:

  • O mapeamento entre URLs e o código-fonte (quais URLs invocam quais classes e métodos) fica em um arquivo de rotas (Rails e Django).
  • A lógica de negócio fica no Controller (Rails) ou em um View (Django)
  • O acesso ao banco de dados fica encapsulado por uma biblioteca implementando o padrão Active Record (Rails e Django)
  • A saída HTML é gerada dentro de Views (Rails) ou Templates (Django)

Roteamento de requisições

O roteamento de requisições, em ambos os frameworks, declara qual URL invoca qual ação em qual Controller. A grande difereça aqui, que para mim favorece o Rails, é que o Django exige que façamos a configuração de todas essas rotas, por mais simples e óbvias que possam ser.

Imagine a seguinte URL:

http://exemplo.com/users/view/1

O Rails utiliza um esquema default de mapeamento que deduz, a partir da URL acima, o seguinte:

  • controller = users (será instanciada a classe UsersController)
  • action = view (será invocado o método view nessa instância)
  • id = 1 (dentro desse método estará disponível a variável local params[:id])

Para uma aplicação simples, esse esquema pode ser suficiente para todas as suas necessidades. Se não for, você pode definir novas regras, que possuem prioridade dependente da ordem em que são declaradas.

Com o Django, por outro lado, todos os mapeamentos precisam ser definidos por meio de expressões regulares. Não é complicado (graças à documentação), mas se podemos ter isso de graça no Rails, fica chato ter que voltar a se preocupar com esse tipo de coisa dentro do Django.

Rails Controllers versus Django Views

Para comparar as duas abordagens vamos considerar que a nossa lógica de negócio envolve a manipulação de uma tabela “users”. Estamos implementando a ação “list”, que deve preparar, para exibição, todos os registros dessa tabela.

Em Rails (no arquivo app/controllers/users_controller.rb):

class UsersControler < ActiveRecord::Base
  def list
    @user_pages, @users = paginate :users, :per_page => 10
  end
end

Em Django (no arquivo nomedasub_aplicacao/views.py):

from django.shortcuts import render_to_response

def list(request):
  users = User.objects.all()
  return render_to_response('users/list.html', {'users': users})

Diferenças fundamentais:

  • Um Rails Controller é definido como uma classe, enquanto um Django View é um conjunto de funções
  • O arquivo que define o Rails Controller não costuma requerir outros arquivos ou pacotes. O arquivo que define as funções de um Django View, por sua vez, costuma requerir um ou mais pacotes.
  • Os métodos de um Rails Controller não recebem parâmetros vindos do framework. As funções de um Django View sempre recebem um objeto HttpRequest.
  • Todas as variáveis que se deseja utilizar em um Rails View devem ser inicializadas, dentro do Rails Controller, como variáveis de instância (@users, no exemplo). Esse mesmo comportamento não pode ser alcançado com o Django: é necessário passar as variáveis explicitamente para um Django Template, na forma de um dicionário Python (’nome da variável’: valor). No exemplo acima é passado o dicionário {'users': users}.
  • As convenções de Rails eliminam a necessidade de se declarar, dentro de um método do Rails Controller, qual View deve renderizar os objetos carregados no Controller. No Django essa declaração é obrigatória. Em Rails, é claro, se você precisar renderizar um View que não segue a convenção, você também pode fazê-lo facilmente.

Observações:

  • O tutorial básico do Django não indica como inicializar, dentro do Django View, um objeto que encapsule a paginação referente aos registros recuperados. Deve haver uma forma simples de fazer isso, mas fiquei impressionado por isso não estar presente no tutorial.

Rails Views versus Django Templates

É uma preocupação comum aos dois frameworks, por serem adeptos do MVC, localizar a renderização de código HTML em um arquivo distinto do arquivo que interage com o banco de dados. Esses arquivos possuem, em Rails, extensão .rb, enquanto no Django possuem extensão .html. Isso pode ser um problema para certos editores de texto. Eu, por exemplo, trabalho no editor Vim. Com alguns pacotes de configuração a coloração da sintaxe dos meus arquivos .rb fica perfeita. Já com arquivos .html do Django eu não tive a mesma sorte.

Seguindo o exemplo deste artigo, um trecho do Rails View que lista os usuários poderia ser o seguinte:

<ul>
<% for user in @users do %>
  <li><%= user.full_name %></li>
<% end %>
</ul>

E o mesmo trecho, para um Django Template:

<ul>
{% for user in users %}
  <li>{{ user.full_name }}</li>
{% endfor %}
</ul>

Nos dois frameworks um view/template pode ser mais complexo do que isso, incluindo estruturas de desvio condicional, por exemplo.

Diferenças fundamentais:

Observações:

  • Em Ruby, o código user.full_name é válido, e significa invocar o método full_name do objeto user. Mas em Python, ao contrário do que o exemplo acima dá a atender, a declaração user.full_name não invoca o método full_name de user. É o sistema de templates do Django quem faz esse processamento. Primeiro o Django verifica se full_name é uma chave do dicionário user (mesmo que user não seja um dicionário). Se não for, então verifica se é um atributo do objeto user. Se não for, então invoca full_name como um método.
  • No Django é possível utilizar um sistema de templates diferente do sistema provido pelo framework. Em Rails é possível apresentar os templates em formato XML.

Rails Models versus Django Models

Tanto em Rails quanto em Django a camada de abstração do banco de dados implementa o mesmo padrão de projeto: Active Record, proposto por Martin Fowler. Apesar disso, as duas implementações são bastante diferentes.

A implementação Ruby faz uso intenso de metaprogramação para, “automagicamente”, definir atributos e métodos dos Models. Exemplo:

class User < ActiveRecord::Base
  has_and_belongs_to_many :communities
end

No exemplo acima, declaramos a classe User, o nosso Model. Quando o código é executado, nossa classe ganha diversos métodos e atributos, referentes a todas as colunas presentes na tabela “users” do banco de dados. A invocação do método has_and_belongs_to_many também cria diversos métodos para a classe. Como todas essas definições são feitas em tempo de execução, a performance fica prejudicada. É uma escolha que se faz: facilidade e agilidade para definição de seus modelos em troca de uma performance um pouco inferior.

Mas há outra vantagem nessa abordagem: uma mudança no banco de dados não significa, obrigatoriamente, uma mudança na definição da nossa classe. No início do projeto, quando ainda não se sabe quais campos nossa tabela deve ter, isso é uma grande vantagem para o desenvolvimento. Essa filosofia de evitar repetições está presente por todo o Rails, e é conhecida como DRY (Don’t Repeat Yourself, ou “Não se repita”).

A abordagem do Django é inversa: o nosso Model é quem diz qual será a estrutura da nossa tabela. Um exemplo:

from django.db import models

class User(models.Model) # Em Python isso significa que User extende models.Model
    first_name = models.CharField(max_length=60)
    last_name = models.CharField(max_length=60)

Quando queremos que o banco de dados se modifique, precisamos rodar um comando do Django que processa as definições de modelos e cria as respectivas tabelas (se elas não existirem):

> python manage.py syncdb

A performance da implementação Django para o Active Record é, em teoria, superior a do Ruby, uma vez que as classes não são definidas por metaprogramação. Isso pode ser um fator determinante para aqueles que necessitam de alto desempenho (embora isso varie muito dependendo da configuração do seu servidor - Apache servindo em modo mod_python ou fastcgi, por exemplo).

Observações: * No Django é possível utilizar uma outra camada de abstração de banco de dados que não essa apresentada anteriormente. Segundo a documentação essa troca é feita de maneira trivial. Já no Rails, tentar utilizar uma outra biblioteca para isso provavelmente não vale a pena, pelo trabalho que daria. Mas será que alguém realmente não gosta da ActiveRecord do Ruby?

Evolução do banco de dados

No Rails, controlar a evolução do banco de dados é muito simples (comparado com Django): cada modificação na estrutura do banco de dados demanda a criação de uma nova migração. Imagine, por exemplo, que o nosso sistema já está em produção e o cliente nos diz que ele precisa de mais uma informação a respeito do usuário: sua data de nascimento.

Com o seguinte comando criamos uma nova migração:

> ruby script/generate migration add_birthday_to_users

Esse comando gera o arquivo db/migrate/???_add_birthday_to_users, onde ??? é o número dessa migração (o Rails o controla automaticamente). Abrindo esse arquivo editamos o código Ruby que realiza a nossa tarefa. Supondo que estejamos trabalhando com controle de versão de software, fazemos o commit da nossa modificação e em seguida vamos até o servidor de produção para aplicar a mesma modificação nesse banco de dados. Atualizamos o código da aplicação (com “svn up”, por exemplo), e em seguida executamos:

> rake db:migrate

Esse comando irá trazer o banco de dados para a última versão disponível.

Agora você me pergunta: e como faço essa mesma operação no Django? E eu te responderei: faça tudo sozinho, em SQL puro. É sério. No Django você controla a evolução do seu banco de dados manualmente. Para o exemplo acima isso pode até não ser um problema, pois nossa modificação era trivial. Agora considere modificações maiores, que envolve transferências de dados entre tabelas. E se sua aplicação for independente de banco de dados? Bem, você terá que escrever o SQL específico de cada SGBD.

Esse é um dos fatores que, na minha opinião, conta muito contra o Django, em sua versão 0.96.

Testes de software (TDD e BDD)

Este tópico pode ser irrelevante para quem não aplica desenvolvimento dirigido por testes, ou simplesmente teste de software. Se você se inclui nesse grupo, então não ficará triste por saber que não há uma forma muito simples de seguir essa metodologia no Django (ao menos o tutorial de iniciação no framework não dá nenhuma dica nesse respeito), e buscas pelo Google não me ajudaram muito também.

No Rails as opções são várias: você pode utilizar a biblioteca fornecida pelo próprio Rails, Selenium, RSpec (que eu utilizo e recomendo), entre outras. RSpec, por exemplo, é integrado ao Rails por meio de um plugin, muito fácil de instalar e de começar a utilizar.

A falta de TDD e BDD integrado ao Django é, na minha opinião, mais um fator que conta contra o Dango.

Particularidades do Django

Uma das características que os desenvolvedores do Django mais exaltam no framework é a interface administrativa criada “automagicamente” para os Models definidos por você. Essa interface é realmente poderosa, pois é capaz de manipular relacionamentos 1-N que o scaffold básico do Rails ignora. Para colunas que representam datas você ganha até caléndários Ajax para definir os valores.

No entanto, o visual dessa interface administrativa dificilmente será aquilo que você realmente deseja para a sua aplicação. De qualquer modo, melhor tê-la do que não tê-la.

Para o Rails há um plugin que reproduz funcionalidade semelhante: Auto Admin (o site estava fora do ar quando acessei pela última vez).

Livros sobre o assunto

Buscando por “rails” no site da Amazon encontrei em torno de 20 livros sobre o assunto. Já sobre Django, encontrei apenas 2. A situação do Django pode mudar nos próximos meses, principalmente se a versão 1.0 do Django for lançada.

A documentação do Django é muito boa, e pode ser acessada aqui. No momento, no entanto, a documentação do Rails é incomparável, com uma grande quantidade de tutoriais espalhados pela internet, blogs sobre o assunto e muitos livros disponíveis.

Conclusões

Este artigo teve por objetivo dar uma visão geral das principais diferenças entre a utilização do Rails e do Django em um projeto. Quando comecei a estudar o Django estava disposto a deixar o Rails de lado se o primeiro realmente se provasse mais adequado para o tipo de aplicação que desejo fazer. Mas, no fim das contas, concluí que nenhuma delas facilita a minha vida nesse sentido.

Bom, se nenhuma delas me ajuda no desenvolvimento de uma aplicação extensível, então qual o meu critério de escolha? A que me dá mais vantagens durante o desenvolvimento (mas outras pessoas podem escolher performance, deployment, familiaridade com a linguagem etc.). Rails, nesse sentido, está anos luz à frente. A definição de tabelas utilizando sintaxe Ruby, a definição de modelos de forma DRY, o controle da evolução do banco de dados de forma trivial, os testes com RSpec facilmente integrados à aplicação…

Se você é um programador Python experiente, recomendo que use o framework por umas semanas e tire suas próprias conclusões. Você pode se sentir muito à vontade com ele e não sentir falta dessas coisas que listei.

Se você é um programador Ruby, no entanto, recomendo que continue com o Rails. Mas essa é apenas a minha opinião.

Referências

Atualização 1 (03/11/2007): alguns trechos de código-fonte tinham problemas (apontados pelo Marcelo Minholi nos comentários). Verifique também os comentários de Andrews Medina para conhecer mais sobre as opções do Django para TDD, que não foram abordadas no artigo por não estarem explícitas na documentação oficial na época em que esta comparação foi escrita.


Ruby on Rails pode não ser o framework adequado para o seu projeto

Nos últimos dias passei por uma “crise existencial” enquanto procurava justificativas para o meu TCC. Como alguns podem lembrar o tema já foi “Adaptabilidade em um LMS baseado em SCORM”, mas eu acabei tirando a parte de “adaptabilidade” pois não vou conseguir dar conta de fazer tanta coisa até outubro (preciso ainda confirmar esse assunto com meu orientador, na verdade).

De qualquer modo o restante do título poderia ficar intacto, “Um LMS baseado em SCORM”. Pois bem, aí entra Ruby. Já há alguns anos eu escrevo pequenas coisas em Rails, mais para aprender os conceitos por trás do framework do que para fazer algo realmente útil. Mas com o TCC batendo à porta ficou cada vez mais necessário escrever meu primeiro projeto sério com Rails, que é o meu LMS, batizado “Astra”.

Agora como você justifica a escrita de um LMS em Rails?, quando há um outro, em PHP, já próximo da versão 2.0, muito bom em termos de funcionalidade, com uma grande comunidade de usuários e desenvolvedores e, principalmente, com uma arquitetura modular (sim, estou falando do Moodle). Pois bem, como justificar?

Eu pensei, é claro, em várias coisas:

  • Ruby é uma linguagem mais poderosa (Closures, Módulos, Metaprogramação etc.)
  • Rails é um framework de peso no mundo Open Source, e sua arquitetura MVC junto de bibliotecas como ActiveRecord e a integração com RSpec promovem desenvolvimento ágil e orientado por testes, uma boa prática quando se quer alcançar maior produtividade e menos bugs.

Mas fica faltando algo… vocês percebem? Cadê a parte que diz que uma aplicação Rails pode ser feita de forma facilmente estensível? Quero dizer, da mesma forma que o Moodle pode ser estendido com módulos de atividades, filtros de texto e caixinhas, como podemos escrever uma futura aplicação Rails?

A resposta: com muito, muito esforço. O autor do Radiant CMS, John Long, comenta isso em seu blog. De fato, ele até defende mudanças profundas na estrutura de diretórios do Rails. Essa discussão está em seu blog, e também se alastrou pela lista de discussão do Rails. O que a comunidade “core” do Rails disse? “Desenvolvimento baseado em componentes não nos interessa. Quando houver mais desenvolvedores com esse mesmo desejo - justificando uma mudança -, então pensamos no caso”.

Justificando este post: nessa mesma discussão alguns desenvolvedores Rails mais experientes deixam claro que o Rails foi projetado para criar projetos rápido “do zero”, e não para criar grandes projetos baseados em componentes, tais como Moodle, Wordpress ou Drupal. Não que isso seja impossível em Rails, mas há frameworks muito mais adequadas para isso.

Quero dizer, como justificar o Rails como um bom substituto para a arquitetura do Moodle, se nessa troca as melhores características desse último (modularização, por exemplo) são perdidas?

Então o Rails não parece ser a minha melhor opção, certo? Errado. Apesar de “ruim”, precisa existir algo melhor para que o Rails deixe de ser minha melhor opção (nesse projeto em particular). E parece que existe: o Django, um framework web escrito em Python, que parece ser baseado totalmente no desenvolvimento orientado a componentes - exatamente aquilo que estou procurando para o meu LMS.

Sobre o Django eu não posso dizer mais coisas, pois ainda não pesquisei nada sobre ele. Mas essa é a minha próxima lição de casa, e volto a falar mais sobre ele nos próximos posts.

Links interessantes sobre (falta de) modularidade no Rails: