Outro dia, tive um problema chatíssimo com o ActiveRecord: eu precisava criar um código de autenticidade, que conteria várias matrículas, que conteria várias disciplinas. O problema é o seguinte: Essas matrículas só seriam válidas se, por exemplo, o total de créditos das disciplinas não ultrapassassem um determinado número. Logo, resolvi fazer a validação no modelo de autenticidade, da seguinte forma:

class Autenticidade < ActiveRecord::Base
  MAX_CREDITOS = 40
  has_many :matriculas
  has_many :disciplinas, :through => :matriculas
  validate :credito_alto?

def credito_alto?
    total = disciplinas.inject(0) { |r, v| r += v.creditos }
    errors.add(:disciplinas, 'créditos acima do permitido') if total > MAX_CREDITOS
  end
end

class Matricula < ActiveRecord::Base
  belongs_to :disciplina
  belongs_to :autenticidade
end

class Disciplina < ActiveRecord::Base
end

Ok, o problema agora é o seguinte: Se eu crio uma autenticidade, com o parâmetro :disciplina_ids => [1, 2], por exemplo, e a autenticidade não é válida, nenhuma nova matrícula é criada – como deveria ser. Agora, se eu atualizo uma autenticidade, com o  autenticidade.update_attributes( :disciplina_ids => [1, 2]), e essa autenticidade não é válida, o ActiveRecord salva as matrículas. Nesse caso, eu tenho uma inconsistência na base de dados feia – e precisava encontrar uma alternativa que não envolvesse muitos hacks, coisa que ainda não encontrei. Mas isso me estimulou a pesquisar o DataMapper.

O DataMapper é um ORM assim como o ActiveRecord – porém com um diferencial que (para alguns) é interessante: Ele só lança os comandos SQL quando você precisa. Nossa autenticidade (fora as validações) ficaria assim:

class Autenticidade
  include DataMapper::Resource

MAX_CREDITOS = 40
  property :id, Serial
  property :codigo_autenticidade, String
  has n, :matriculas
  has n, :disciplinas, :through => :matriculas
end

Para alguns, é uma vantagem declarar as propriedades no próprio código – dizem que é mais simples de ver o que há nas suas tabelas. Eu, discordo categoricamente disso – o código-fonte não deveria fazer o que a documentação faz (se você não tem um dicionário de dados, é culpa sua, não do ActiveRecord). Mas uma coisa legal é: Se vc fizer, por exemplo: a = Autenticidade.all[0], o DataMapper gera apenas o SQL que você precisaria (nesse caso, SELECT * FROM autenticidades LIMIT 1). Isso funciona, de acordo com a documenação do DataMapper, inclusive para relações:

autenticidades = Autenticidade.all #Não gera nenhum SQL ainda
autenticidades.each do |autenticidade| #Gera um SELECT * FROM autenticidades
  p autenticidade.matriculas #Gera um SELECT para listar TODAS as matrículas de TODAS as autenticidades, apenas na primeira iteração
end

Ok, isso elimina o tal do problema n+1 (No ActiveRecord, isso geraria um SELECT para cada autenticidade). O problema é: isso funciona muito bem, mas no has n, :through, o problema do n+1 existe! E, para piorar, um Autenticidade.all :include => :disciplinas simplesmente não existe, logo eu não tenho como sair desse erro. No fim, a vantagem virou uma desvantagem.

Outra vantagem que eu tinha visto era a possibilidade de criar adaptadores para coisas que não são bases de dados relacionais – imagine, por exemplo, você usando o DataMapper como um ORM para LDAP, MySQL, REST e YAML, tudo ao mesmo tempo – fazer associações com isso seria uma maravilha, falar que seu usuário do MySQL tem uma entrada LDAP, usando as associações do próprio DataMapper. Porém, mais uma vez, as coisas não funcionaram muito bem: has n só funciona quando as bases de dados estão no mesmo adaptador, talvez para otimizar os SQLs com JOINs, etc. Logo, mais uma vantagem que eu vi foi sub-utilizada.

No fim, a única vantagem que o DataMapper me deu foi a possibilidade de fazer isso aqui:

autenticidade = Autenticidade.first
autenticidade.disciplinas << Disciplina.get(:id => 1)
autenticidade.disciplinas << Disciplina.get(:id => 2)
autenticidade.save #Se for falso, NÃO salva as disciplinas

Pois é, eu gostaria mais de ver as vantagens do DataMapper no ActiveRecord do que o inverso…


5 Comments

Ícaro Vinícius Torres Silva · 2009-10-06 at 17:33

Seria possível um merge entre os 2 módulos?

Maurício Szabo · 2009-10-09 at 23:06

Duvido, porque são duas bibliotecas diferentes, com visões diferentes. Aparentemente o ActiveRecord é menos extensível e mais “completo”, enquanto o DataMapper é minimalista (por opção) e para completar as funcionalidades dele que se usam plugins…

Fabio · 2012-11-12 at 10:20

Murício,

Sou novo em Ruby, mas ontem estava estudando essa questão de validação e li que quando se usa update_attributes a validação não e disparada. Daí a inconsistência no seu banco de dados.

Lazy Evaluation « Maurício Szabo · 2009-11-29 at 22:34

[…] de algo pode te dar maior fluidez em alguns outros momentos. Já citei anteriormente sobre o DataMapper, que na hora de fazer uma busca retorna apenas uma “promessa” que que irá buscar, e se […]

Lazy Evaluation « Maurício Szabo · 2009-11-29 at 22:34

[…] de algo pode te dar maior fluidez em alguns outros momentos. Já citei anteriormente sobre o DataMapper, que na hora de fazer uma busca retorna apenas uma “promessa” que que irá buscar, e se […]

Comments are closed.