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.