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.