Posted by Coutinho Sun, 22 Apr 2007 01:11:00 GMT
Pessoal uma coisa interessante e seimples de fazer que eu vi no rails e queria compartilhar com vocês são as “associações polimórficas”.
O que é uma associação polimórfica?
Uma associação polimórfica é quando uma tabela pode ser relacionada pelo mesmo caminho a diversas outras. Por exemplo, você tem uma tabela de fornecedores, uma de clientes e uma de números de telefone e essa tabela de telefones tem uma unica associação chamada dono_do_telefone, que hora pode ser um fornecedor, hora pode ser um cliente.
Antes de conhecer o rails e essa solução eu faria uma coisa realmente feia para resolver esse problema, por isso nem vou comentar aqui.
Botando a mão na massa
Vamos ver como fica isso no rails.
Primeiro vou apresentar a vocês o meu PC. Ele se chama Yoda. Porque de tão velhinho ele me lembra o “Mestre Yoda” de guerra nas estrelas.
Então abaixo eu vou gerar os 3 migrates para criar as tabelas que preciso.
(coutinho@yoda - ~/workspace/teste @01:09:05)
0: script/generate model cliente
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/cliente.rb
create test/unit/cliente_test.rb
create test/fixtures/clientes.yml
exists db/migrate
create db/migrate/001_create_clientes.rb
Nossa, passa de 1 da manha então para eu não passar a noite toda aqui repitam o passo acima para criar os models de fornecedores e telefones.
Uma dica, para fornecedor usem o inflect para informar o plural correto da palavra de forma que o rails não use fornecedors ao invés de fornecedores.
Abaixo temos os migrates de nossas 3 tabelas:
class CreateClientes < ActiveRecord::Migration
def self.up
create_table :clientes do |t|
t.column :nome, :string
end
end
def self.down
drop_table :clientes
end
end
class CreateFornecedores < ActiveRecord::Migration
def self.up
create_table :fornecedores do |t|
t.column :nome_fantasia, :string
end
end
def self.down
drop_table :fornecedores
end
end
class CreateTelefones < ActiveRecord::Migration
def self.up
create_table :telefones do |t|
t.column :dono_telefone_id, :integer
t.column :dono_telefone_type, :string
t.column :fone, :string
end
end
def self.down
drop_table :telefones
end
end
Após isso vamos rodar um:
(coutinho@yoda - ~/workspace/teste @01:09:22) 0: rake db:migrate
Vamos agora mapear o relacionamento em nossos models.
Vamos ver como ficaria o model de cliente:
class Cliente < ActiveRecord::Base
has_many :telefones
end
No model de fornecedores faremos exatamnte a mesma coisa, vejam:
class Fornecedor < ActiveRecord::Base
has_many :telefones
end
O código acima não em segredo e conhecido de quem já leu o mmínimo de rails, o segredo da associação polimórfica então está no nosso model telefone.
Vamos conferir:class Telefone < ActiveRecord::Base belongs_to :dono_telefone, :polymorphic => :true end
Nossa, que segredão. Com o parâmetro :polymorphic que usamos no belongs_to o rails sabe que o nossa relação é “mutante” então para saber qual é o tipo de objeto ao qual realmente o telefone vai estar associado o active record vai usar aquele campo dono_telefone_type para gravar o nome da associação quando gravar-mos um registro e vai usa-lo tambem na hora em que tentar-mos ler uma associação já gravada.
Será que isso funciona?
Vamos ver como fica isso no console:
(coutinho@yoda - ~/workspace/teste @01:24:24)
1: script/console
Loading development environment.
>> c = Cliente.create(:nome => 'Coutinho')
=> #<Cliente:0xb764ab24 @errors=#<ActiveRecord::Errors:0xb75d9f00 @errors={}, @base=#<Cliente:0xb764ab24 ...>>, @attributes={"nome"=>"Coutinho", "id"=>1}, @new_record=false, @new_record_before_save=true>
>> t = Telefone.new(:fone => '8588885555')
=> #<Telefone:0xb75c79b8 @attributes={"dono_telefone_id"=>nil, "fone"=>"8588885555", "dono_telefone_type"=>nil}, @new_record=true>
>> t.dono_telefone = c
=> #<Cliente:0xb764ab24 @errors=#<ActiveRecord::Errors:0xb75d9f00 @errors={}, @base=#<Cliente:0xb764ab24 ...>>, @attributes={"nome"=>"Coutinho", "id"=>1}, @new_record=false, @new_record_before_save=true>
>> t.save
=> true
>> t
=> #<Telefone:0xb75c79b8 @errors=#<ActiveRecord::Errors:0xb759e180 @errors={}, @base=#<Telefone:0xb75c79b8 ...>>, @dono_telefone=#<Cliente:0xb764ab24 @errors=#<ActiveRecord::Errors:0xb75d9f00 @errors={}, @base=#<Cliente:0xb764ab24 ...>>, @attributes={"nome"=>"Coutinho", "id"=>1}, @new_record=false, @new_record_before_save=true>, @attributes={"dono_telefone_id"=>1, "fone"=>"8588885555", "id"=>1, "dono_telefone_type"=>"Cliente"}, @new_record=false>
>> t.dono_telefone
=> #<Cliente:0xb764ab24 @errors=#<ActiveRecord::Errors:0xb75d9f00 @errors={}, @base=#<Cliente:0xb764ab24 ...>>, @attributes={"nome"=>"Coutinho", "id"=>1}, @new_record=false, @new_record_before_save=true>
>>
Vamos traduzir o que aconteceu acima:
- Criei um cliente chamado Coutinho
- Criei um numero de telefone
- Associei o número de telefone ao cliente recem criado usando: “t.dono_telefone = c”
- Salvei e conferi o resultado :)
O processo parece óbvio, e é. Porém o rails não conseguia depois encontrar o dono_telefone para esse registro se não tivesse sido informado o tipo da classe (do objeto) que você está relacionando.
Para quem não reparou os atributos do registro telefone:
@attributes={"dono_telefone_id"=>1, "fone"=>"8588885555", "id"=>1, "dono_telefone_type"=>"Cliente"}
Vamos fazer agora nosso teste com fornecedor e conferir o resultado final se funciona mesmo.
>> f = Fornecedor.create(:nome_fantasia => 'Coutinho Comércio e Importação')
=> #<Fornecedor:0xb75398c0 @errors=#<ActiveRecord::Errors:0xb7533f9c @errors={}, @base=#<Fornecedor:0xb75398c0 ...>>, @attributes={"id"=>1, "nome_fantasia"=>"Coutinho Comércio e Importação"}, @new_record=false, @new_record_before_save=true>
>> tt = Telefone.new(:fone => '85 é o DDD do ceará')
=> #<Telefone:0xb751b8d4 @attributes={"dono_telefone_id"=>nil, "fone"=>"85 é o DDD do ceará", "dono_telefone_type"=>nil}, @new_record=true>
>> tt.dono_telefone = f
=> #<Fornecedor:0xb75398c0 @errors=#<ActiveRecord::Errors:0xb7533f9c @errors={}, @base=#<Fornecedor:0xb75398c0 ...>>, @attributes={"id"=>1, "nome_fantasia"=>"Coutinho Comércio e Importação"}, @new_record=false, @new_record_before_save=true>
>> tt.save
=> true
>> tt.dono_telefone
=> #<Fornecedor:0xb75398c0 @errors=#<ActiveRecord::Errors:0xb7533f9c @errors={}, @base=#<Fornecedor:0xb75398c0 ...>>, @attributes={"id"=>1, "nome_fantasia"=>"Coutinho Comércio e Importação"}, @new_record=false, @new_record_before_save=true>
>>
Dessa vez acredito que nao precisa de tradução.
Só como uma brincadeirinha final agora quem quizer pode tentar rodar esse código no console:
for tel in Telefone.find_all
if tel.dono_telefone_type=='Cliente'
puts "O dono do telefone '#{tel.fone}' é o cliente: #{tel.dono_telefone.nome}"
else
puts "O dono do telefone '#{tel.fone}' é o fornecedor: #{tel.dono_telefone.nome_fantasia}"
end
end
Até a próxima pessoal.
