Finalmente, o Rails 3 foi lançado, e junto com ele vieram diversas funcionalidades legais: maior suporte para frameworks Javascript, mais rápido, mais agnóstico, etc etc… mas na minha opinião, a maior vantagem está no ActiveRecord 3.0
O ActiveRecord ganhou uma dependência chamada Arel, uma biblioteca de álgebra relacional. Muitos blogs já falaram sobre o assunto, então não vou me extender, vou direto ao ponto: Ruby é uma linguagem orientada a objeto, e ela é BOA no que faz. SQL é uma linguagem para fazer buscas, e devo dizer, ela também é BOA no que faz. Ruby entende objetos, SQL entende tabelas, e, bom, misturar os dois deveria ser muito mais transparente do que é. Por exemplo, o código a seguir:
maiores = Pessoa.maiores_de_idade homens = Pessoa.homens return maiores + homens
Pensando que as duas primeiras linhas retornam um “scope”, isso funcionará da seguinte forma, hoje, tanto no Rails 2 e 3: buscará os maiores de idade, buscará os homens (dois SELECTs), e então o Ruby concatenará os dois resultados. Isso é PÉSSIMO, porque a concatenação não deveria ser feita no Ruby, a concatenação pode trazer dois resultados do mesmo registro, e principalmente, porque a base de dados busca melhor se for feito apenas UM “SELECT”, não dois. Aí, entra o Arel, uma biblioteca de álgebra relacional, na qual essas condições ficam mais fáceis. Por exemplo:
maiores = Pessoa.maiores_de_idade.arel homens = Pessoa.homens.arel return Pessoa.where(Arel::Predicates::Or.new(maiores, homens))
O problema evidente do código acima é a conversão constante: primeiro que o código cria alguns objetos do ActiveRecord, converte para Arel, e volta para ActiveRecord. Como só estou usando o “where”, não suporta os “joins”, “includes”, ou qualquer outra coisa. Seria muito melhor que o código fosse bem mais simples.
Entra, então, “operator overload”.
Qualquer busca do ActiveRecord 3 retorna um objeto do tipo ActiveRecord::Relation. Basta então sobrepor estes operadores, e dentro deles montar as queries. Uma forma de fazer isso é:
class ActiveRecord::Relation def +(other) clone.tap do |o| predicate = Arel::Predicates::Or.new(self.where_values, other.where_values) cond = build_where(predicate) o.where_values = [cond] end end end
Ainda não suporta “joins” e “includes”, mas pelo menos suporta certas queries tipo:
maiores = Pessoa.where :idade => 18 homens = Pessoa.where :sexo => 'masculino' return maiores + homens #Gera UM SQL.
Estou trabalhando em uma gem, “Arel Operators”, que automatizará o processo. PORÉM, eu não gosto muito de “monkey-patch” sem autorização, então nessa GEM é necessário usar:
class Pessoa < ActiveRecord::Base extend ArelOperators end
Para poder usá-la. O que ela faz é simples: os métodos “where” e “scoped” retornam agora um ActiveRecord::Relation, com o módulo “ActiveRecord::Operators” incluso. Não fiz benchmarks para saber quais as implicações de velocidade, se há alguma, mas é uma solução melhor, na minha opinião, do que sair fazendo “monkey-patch” de tudo, e dessa forma causando possíveis problemas quando se deseja que algum método funcione da forma antiga. O código está em http://github.com/mauricioszabo/arel_operators, e para as próximas versões, suporte à joins e includes.
1 Comment
ArelOperators e Buscas sem SQL | Maurício Szabo · 2010-09-19 at 23:57
[…] o pessoal que foi no encontro do Guru-SP, apresentei um pouco do trabalho. A idéia, conforme o post anterior sobre o assunto, é tornar o Arel mais transparente na hora de formar queries no ActiveRecord, […]
Comments are closed.