Uma breve introdução dos meus conhecimentos utilizados neste post:
Sou apenas um intusiasta, (infelizmente) não trabalho com rails nem ruby.. mas gosto muito e tento sempre estar estudando o máximo possível.
Não sei muito de padrões de projetos, quero estudar, quero me aprimorar, mas o pouco que eu sei me ajuda em alguns casos.
O que me levou a escrever este post:
Pouco tempo atrás tive que fazer um trabalho da faculdade que envolvia modelagem e desenvolvimento de um pequeno sistema, resolvi fazer em Ruby on Rails pois é a tecnologia que atualmente eu conheço e gosto mais.... Porém como eu só estudo, sigo tutoriais e tals nunca havia passado pela necessidade de implementar uma modelagem um pouco mais complexa, como por exemplo uma hierarquia de negócios com herança. Quando fui implementar me deparei com o fato do ActiveRecord, ORM do rails, permitir apenas STI(Simple table Inherit), ou seja, ele cria uma única tabela para toda a hierarquia de classes, com uma coluna que funciona como uma flag(sinalização) para identificar quais registros são de quais tipos. Exemplificando:
Imagine que na sua modelagem você tenha a classe Pessoa e a classe PessoaFisica e PessoaJuridica que herdam de Pessoa. Então temos:
class Pessoa < ActiveRecord::Baseendclass PessoaFisica < Pessoaendclass PessoaJuridica < Pessoaend
Bom o que o ActiveRecord faz ao rodar o migrate é criar uma única tabela pessoas e nela uma coluna type que ele colocará 'PessoasFisicas' ou 'PessoasJuridicas'. Bem o que ocorre é que os registros de PessoasFisicas vão ficar com o campo cnpj = null e PessoasJuridicas com cpf = null.
Para pequenas massas de dados isso não é muito problema, mas para grandes massas de dados isso pode ser incomodo para bkps, etc. Uma solução seria criar uma tabela para cada classe da hierarquia. Isso é possível usando relacionamentos, 1-1, 1-n, n-m...
Porém um amigo me chamou a atenção para um outro "problema" do ActiveRecord, dentro do model não existe os códigos dos attributos, qual o problema nisso?!
Imagine um grande sistema desenvolvido, chega um estagiario novo para trabalhar na manutenção deste, ele abre o código e onde é para estar as regras de negócio, no model, ele não consegue saber quais atributtos existem!! Então ele vê algumas validações mas ele não conhece aquela entidade, e se depender do model gerado pelo ActiveRecorde ele nem vai conhecer pois lá não está explicito quais atributos aquela entidade possui...
Bem levando tudo isso em conta fui dar uma olhada se a única opção de ORM com ruby e com rails era o ActiveRecord, e descobri que não.. O Merb, um outro framework web para Ruby usava o DataMapper. Comecei então a ler sobre este ORM e descobri que ele é mais rápido que o ActiveRecord e que em seus models existe, obrigatoriamente a especificação dos atributos daquela entidade. Maravilha!!! Continuei lendo e travei: ele também segue o padrão STI, sou só eu ou mais alguém não vai muito com a cara desse tal de STI... Bem, mas como quem tá na chuva é para se molhar tentei implementar algo com hierarquia.. Não consegui, mas consegui algo que achei muito interessante, consegui fazer os relacionamentos funcionarem de forma mais dinâmica.
Voltando no exemplo de Pessoas, Imagine os seguintes models:
class Pessoainclude DataMapper::Resource
property :id, Serialproperty :nome, Stringproperty :observacao, String
has 1, :pessoafisica, :model => 'PessoaFisica'has 1, :pessoajuridica, :model => 'PessoaJuridica'end
class PessoaFisicainclude DataMapper::Resource
property :id, Serialproperty :cpf, String
belongs_to :pessoaend
class PessoaJuridicainclude DataMapper::Resource
property :id, Serialproperty :cnpj, String
belongs_to :pessoaend
Ai temos nossos models Pessoa, PessoaFisica e PessoaJuridica, eles estão relacionados, 1-1, uma pessoa possui uma pessoafisica ou uma pessoajuridica. Até ai nada de mais, porém para acessar esses dados poderiamos fazer assim:
# crio a instancia de pessoafisica>> pf = PessoaFisica.new
# a pessoa fisica possui uma pessoa que possui um nome, porém quando eu
# tento acessá-lo direto ele me fala que o objeto pessoa ainda não existe
>> pf.pessoa.nome='kassio'NoMethodError: undefined method `nome=' for nil:NilClassfrom (irb):16from :0# Então eu crio a pessoa da pessoafisica>> pf.pessoa = Pessoa.new
# E ai sim consigo acessar o seu nome>> pf.pessoa.nome='kassio'=> "kassio"
Porém mesmo assim para acessar o nome de uma pessoafisica ficou complicado: pf.pessoa.nome, o desenvolvedor tem que pensar que a pessoa física tem uma pessoa que tem um nome!? É estranho isso.. Então o que eu fiz foi incorporar os atributos de pessoa em pessoafisica e pessoajuridica para ficar mais simples de acessar estes atributos, e mantendo uma tabela por classe.
Meu código ficou assim:
# Model Pessoaclass Pessoainclude DataMapper::Resource
property :id, Serialproperty :nome, Stringproperty :observacao, String
has 1, :pessoafisica, :model => 'PessoaFisica'has 1, :pessoajuridica, :model => 'PessoaJuridica'end# Modulo que cria os atributos de Pessoas como atributos da classe e possui# um metodo que salva os atributos numa pessoa novamodule MPessoaPessoa.properties.each do |p|attr_accessor p.nameend
def dados_pessoadados = Hash.newPessoa.properties.each do |p|dados[p.name.to_sym] = self.send(p.name.to_sym)endself.pessoa = Pessoa.create dadosendend# Model PessoaFisica, incluindo o Modulo MPessoa, e com a chamada para# salvar a pessoa antes de salvar a pessoa fisica.class PessoaFisicainclude DataMapper::Resourceinclude MPessoa
property :id, Serialproperty :cpf, String
belongs_to :pessoa
before :save, :dados_pessoaend# Model de PessoaJuridica, com a mesma logica de PessoaFisica.class PessoaJuridicainclude DataMapper::Resourceinclude MPessoa
property :id, Serialproperty :cnpj, String
belongs_to :pessoa
before :save, :dados_pessoaend
Assim podemos acessar os atributos de forma bem mais legível:
# Cria uma pessoafisica>> pf = PessoaFisica.new# A propria pessoafisica possui um nome>> pf.nome='kassio'=> "kassio"# Possui cpf>> pf.cpf='123123123'=> "123123123"# Ao salvar a nova pessoafisica ele salva primeiro os dados de pessoa e logo # em seguida os dados de pessoafisica.>> pf.save~ (0.000025) SET backslash_quote = off~ (0.000003) SET standard_conforming_strings = on~ (0.000003) SET client_min_messages = warning#Aqui salva os dados de pessoa~ (0.000002) INSERT INTO "pessoas" ("nome") VALUES ('kassio') RETURNING "id"#Aqui salva os dados de pessoafisica~ (0.000004) INSERT INTO "pessoa_fisicas" ("cpf", "pessoa_id") VALUES ('123123123', 5) RETURNING "id"=> true #salvo com sucesso>>
Espero que isso possa ser útil para alguém.
Por hoje é só.
=D
Hum... interessantes suas ideias... vc eh muito inteligente :D
ResponderExcluir