Polymorphic Associations

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:

  1. Criei um cliente chamado Coutinho
  2. Criei um numero de telefone
  3. Associei o número de telefone ao cliente recem criado usando: “t.dono_telefone = c”
  4. 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.

11 comments

SQL - Select de 600 linhas

Posted by Coutinho Sun, 18 Feb 2007 15:35:00 GMT

Essa semana um velho conhecido da lista de usuarios de postgresql veio me mostrar uma coisa interessante, uns select de 600 linhas que mais parecem funcoes escritas em pl/pgsql.

Os selects sanguinarios foram gerados pelo hibernate e estao matando o acesso ao banco, tem clausula from com mais de 100 linhas.

Ainda bem que eu nao uso mais Java e mesmo quando usava testei o hibernate e vi que era iviavel :)

Isso so me torna cada vez mais entusiasta do ruby e do rails :)

58 comments

Ruby + Python

Posted by Coutinho Mon, 12 Feb 2007 01:45:00 GMT

A cada dia estou mais seguro de ter escolhido o caminho certo. Bem, python na verdade não foi escolha, foi paixão. E rails quem escolhei foi o meu chefe. :) Mas digamos que eu tenha escolhido essas 2 linguagens.

Recentemente eu quiz usar no rails uma api escrita em python (que faz parte de um sistema em funionamento na empresa). Achei um projeto chamado rupy com ele aplicações ruby podem usar classes python etc. Pois é, e esse é o nome da conferência internacional sobre python e ruby.

O evento vai acontecer na Polonia nos dias 14 e 15 de abril numa cidade chamada POZNA. O objetivo do evento é incentivar o uso de python e ruby (não canso de falar e escrever essas palavras) no leste europeu.

Para maiores informações visitem o site do evento (que é feito em php) http://rupy.wmid.amu.edu.pl

49 comments

Seamonkey

Posted by Coutinho Mon, 12 Feb 2007 01:27:00 GMT

Pessoal voltei ao mozillão. O Seamonkey, antiga suite mozilla, atualmente está sendo mantido pela comunidade.

Uma das novidades é que o navegador da suíte de aplicativos seamonkey agora usa a mesma engine que o firefox 2, enquanto o cliente de email usa a mesma do thunderbird 2 que ainda nem foi lançado.

Como dá para ver a vantagem é que o seamonkey atualmente está andando um pouco a frente do firefox e thunderbird.

Recentemente ao perceber um bug na renderização de páginas com alguns javascripts no firefox 2, eu corri para o seamonkey para comparar o resultado e não deu outra, no seamonkey tudo funcionou perfeito. Foi quando fui comparar e vi que a versão do Gecko (engine de renderização das páginas) no seamonkey estava aproximadamente 1 mês mais nova que no firefox 2.

Aí eu pensei: será que após ter abandonado o velho mozilão o pessoal agora ta usando ele (como bucha de canhão) para testar as features para o firefox?

E parabéns ao pessoal da comunidade Mozilla Brazil, com a qual já tive o prazer de colaborar, por já ter traduzido o seamonkey 1.1 para português do Brasil.

Vale a pena conferir, o site do Mozilla Brasil é http://www.mozilla.org.br

44 comments

PostgreSQL on Rails

Posted by Coutinho Sun, 11 Feb 2007 01:05:00 GMT

Não demorou e alguns fanáticos pelo PostgreSQL como eu, começaram a desenvolver plugins específicos para uso com o PostgreSQL.

É o caso do rails-psql-auth (http://rubyforge.org/projects/rails-psql-auth/) que é um plugin para fazer sua aplicação autenticar os usuarios pelo sistema de autenticação do PostgreSQL.

Minha intenção não é discutir se isso é bom ou ruim, melhor ou pior, apenas mostrar esse excelente trabalho como exemplo.

Outro bom, esse eu gostei e recomendo é o acts_as_tsearch. Para quem não conhece o Tsearch (http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/) é uma extensão para o PostgreSQL que provê o recurso de indexação de textos com recursos fantásticos como ranking dos resultados e um função header que mostra o trecho do conteúdo onde foi satisfeito o critério da busca.

O site do projeto é http://acts-as-tsearch.rubyforge.org/.

Para quem já tem o tsearch instalado e funcionando a instalação do plugin é tão simples quanto a de qualquer um outro e o uso se assemelha a outros actsas_algumacoisa.

Para quem quer dar agilidade a suas buscas e extrair o maximo em funcionalidades => (PostgreSQL + Tsearch2 + Rails + acts_as_tsearch)

46 comments

Eu trabalho com rails

Posted by Coutinho Sun, 28 Jan 2007 13:22:00 GMT

Isso mesmo, o site http://workingwithrails.com/ cadastra desenvolvedores que trabalham com rails, vendo isso resolvi me cadastrar e criar meu profile: http://workingwithrails.com/person/7646-nabucodonosor-coutinho-costa

1 comment

Freeride on Rails

Posted by Coutinho Sun, 28 Jan 2007 04:47:00 GMT

Pessoal, brincando de descobrir ides e editores para rails eu achei o freeride, que acredito que todo mundo conhece, o que eu descobri foi que com ele podemos debugar uma aplicacao rails e acompanhar todo o processo dentro do proprio editor (como no rad rails), bastando para isso iniciar o debug no arquivo script/server.

Vale a pena dar uma conferida no site do projeto em http://freeride.rubyforge.org/

42 comments

HABTM - Has And Belongs to Many

Posted by Coutinho Sun, 28 Jan 2007 04:29:00 GMT

Pessoal, hoje vamos ver como fazer um formulário master detils para uma relação has_one.

Sem enrolação. Vamos usar 2 tabelas uma chamada pessoas e uma chamada pessoa_detalhes.

Primeiro vamos criar um novo projeto chamado … carcara.

rails carcara

Agora vamos rodar o comando “script/generate migration create_pessoas” para gerar nosso migrate.

O seu migrate deve ficar parecido com o código abaixo, claro que você pode por quantos campos quizer, mas como amanha pela manhão eu vou a praia eu não quero me cansar muito hoje neste artigo.


class CreatePessoas < ActiveRecord::Migration
  def self.up
    create_table "pessoas" do |t|
      t.column :nome,     :string
      t.column :email,    :string
    end
  end

  def self.down
    drop_table "pessoas"
  end
end

Agora vamos gerar o migrate para a tabela de detalhes com o seguinte comando:

“script/generate migration createpessoadetalhes”

O migrate para a tabela de pessoas tambem vai ficar bem simples, eu defini apenas 2 campos e os motivos vocês já sabem.


class PessoaDetalhes < ActiveRecord::Migration
  def self.up
    create_table "pessoa_detalhes"do |t|
      t.column :pessoa_id, :integer
      t.column :description, :string
    end
  end

  def self.down
    drop_table "pessoa_detalhes"
  end
end

Simples né? O campo pessoa_id na tabela de detalhes é que vai manter a relação do detalhe com o registro da pessoa.

Agora vamos mandar criar essas tabelas em nosso banco de dados com o comando “rake migrate –trace”.

O –trace é só para ver mais detlahes do que vai acontecer no processo.

Se tudo deu certo seu banco de dados tem as 2 tabelas do nosso exemplo e podemos comecar a brincadeira.

Vamos usar o trestle ou o scaffold generator para iniciar nossa brincadeira. Como a maioria do pessoal deve usar scaffold, vamos de scaffold: script/generate scaffold pessoa.

Podemos repetir o passo acima para gerar o trestle ou scaffold da tabela pessoa_detalhes tambem.

Após gerado nosso código base vamos editar os nossos models para definir a relação um para um entre as 2 tabelas.

O model pessoa deve ficar assim:

class Pessoa < ActiveRecord::Base has_one :PessoaDetalhe # essa linha diz que pessoa tem relacao com um registro em PessoaDetalhe end

A linha

O model pessoa_detalhe deve ficar assim:


class PessoaDetalhe < ActiveRecord::Base
  belongs_to :Pessoa # essa linha diz que PessoaDeatlhe tem ligação com Pessoa
end

Moleza né? Pra quem disse que não ia ter enrolação nesse artigo eu já enrolei de mais também né não? oode Pra quem usou trestle o codigo que vai ser implementado no controller é usado na action new.

Confira:

O create original gerado pelo scaffold era esse:


  def create
    @pessoa = Pessoa.new(params[:pessoa])
    if @pessoa.save 
      flash[:notice] = 'Pessoa was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

Vamos inserir uma linha e meia e vai ficar assim:


  def create
    @pessoa = Pessoa.new(params[:pessoa])
    @pessoa.PessoaDetalhe = PessoaDetalhe.new # Cria o registro dependente PessoaDetalhe
    if @pessoa.save and @pessoa.PessoaDetalhe.save # Salva os 2 registros 
      flash[:notice] = 'Pessoa was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

Para quem usa trestle o próximo codigo vai na action edit.

Abaixo o código gerado pelo scaffold:


  def update
    @pessoa = Pessoa.find(params[:id])
    if @pessoa.update_attributes(params[:pessoa])
      flash[:notice] = 'Pessoa was successfully updated.'
      redirect_to :action => 'show', :id => @pessoa
    else
      render :action => 'edit'
    end
  end

Agora precisamos alterar para que mande atualizar tambem os dados do resgistro filho na tabela pessoa_detalhes. Nossa action action vai ficar assim:


  def update
    @pessoa = Pessoa.find(params[:id])
    if @pessoa.update_attributes(params[:pessoa]) and @pessoa.PessoaDetalhe.update_attributes(params[:pessoa_detalhe])
      flash[:notice] = 'Pessoa was successfully updated.'
      redirect_to :action => 'show', :id => @pessoa
    else
      render :action => 'edit'
    end
  end

Agora que temos nosso create e update pronto para ligar com os 2 registros simultaneamente, vamos para a nossa view ver como vai ficaro nosso form.

Aqui é que está o segredo. Para começão, vamos editar as tags que geram o nosso form. Elas estão nas views new e edit.

O segredo é:

Forms master details, precisam ser definidos com blocos de código formfor e fieldsfor. No nosso caso a grosso modo ficaria algo como:


<% form_for :pessoa, :bla-bla-bla ...... do |f| %>
   aqui vao os campos do model pessoa:
   <%= f.text_field :nome %>
   ....
   <% fields_for :pessoa_detalhe %>

   <% end -%>

<% end -%>

Sendo que no nosso caso temos 4 arquivos separados para mosntar o form final como estah descrito acima, são eles:


app/views/pessoas
   new.rhtml
   edit.rhtml
   _form.rhtml
app/views/pessoa_detalhes
   _form.rhtml

A tag de geração do form em nosso new.rhtml de pessoas vão ficar assim:


<% form_for :pessoa, @pessoa, :url => {:action => "create"} do |f| -%>
  <%= render :partial => 'form', :locals => { :f => f } %>
  <%= submit_tag "Create" %>
<% end -%>

A primeira linha diz que esse form é para o model pessoa e inicio um bloco de código passando o model como parametro para o form, o nome usado para o parametro foi f.

Na segunda linha eu só alterei para que o nosso partial _form tenha acesso a nossa variavel local f que representa nosso model.

A terceira linha é o submit que todo mundo já conhece e que não foi alterada.

E a quarta linha é o fim do bloco de código form_for.

Nosso partial _form também precisa ser alterado, precisamos fazer com que os fields sejam gerados a prtir do parametro que representa o model.

Vamos andar de tráz para frente como caranguejo… hummm… cagangueijo anda é de lado né?

Bem, vamos editar o nosso partial de pessoa_detalhe que vai ser usado dentro do form de pessoas.

vamos deixar ele assim:


<% fields_for :pessoa_detalhe, detalhe do |f| %>
    


<%= f.text_field :description %>

<% end %>

Nosso _form.rhtml de pessoa vai ficar assim:


<%= error_messages_for 'pessoa' %>
<%= error_messages_for 'pessoa_detalhe' %>


<%= f.text_field :nome %>


<%= f.text_field :email %>

<%= render :partial => "/pessoa_detalhes/form", :locals => { :detalhe => @pessoa.PessoaDetalhe } %>

Pronto, com isso nosso new.rhtml faz isso:


<% form_for :pessoa, @pessoa, :url => {:action => "create"} do |f| -%>

Nosso _form.rhtml de pessoas faz isso:


<%= error_messages_for 'pessoa' %>
<%= error_messages_for 'pessoa_detalhe' %>


<%= f.text_field :nome %>


<%= f.text_field :email %>

E o _form.rhtml de pessoa_detalhes faz: <% fields_for :pessoa_detalhe, detalhe do |f| %>


<%= f.text_field :description %>

<% end %>

E o new.rhtml fecha o form com:


<% end -%>

Bom, já deve funcionar. Como tarefa vou deixar para vocês fazerem fincionar a action edit.

2 comments

De cara nova de novo

Posted by Coutinho Sun, 28 Jan 2007 04:18:00 GMT

Ficando ainda mais radical, troquei o WordPress que eh escrigto em php, pelo Typo que eh escrito em rails. :)

Agora soh tenho que postar de novo alguns textos :)

2 comments