Capítulo 12

Associações Polimórficas

"Os negócios são o dinheiro dos outros"

Nesse capítulo você verá como criar uma relação muitos-para-muitos para mais de um tipo de modelo.

12.1 - Nosso problema

O cliente pede para a equipe de desenvolvedores criar uma funcionalidade que permita aos visitantes deixar comentários sobre suas visitas aos restaurantes.

Para complicar a vida do programador, o cliente pede para permitir comentários também em qualificações, permitindo aos usuários do site justificar a nota que deram.

Esse problema poderia ser resolvido de diversas maneiras sendo que trabalharemos em cima de um modelo para representar um comentário, relacionado com restaurantes e qualificações, aproveitando para mostrar como realizar tal tipo de relacionamento.

Seria simples se pudéssemos criar mais uma tabela com o comentário em si e o id da entidade relacionada. O problema surge no momento de diferenciar um comentário sobre qualificação de um sobre restaurante.

Para diferenciar os comentários de restaurantes e qualificações, podemos usar um atributo de nome "tipo".

Em Ruby podemos criar apelidos para um ou mais modelos, algo similar a diversas classes implementarem determinada interface (sem métodos) em java. Podemos chamar nossos modelos Restaurante e Qualificacao como comentáveis, por exemplo.

Um exemplo dessa estrutura em Java é o caso de Serializable - interface que não obriga a implementação de nenhum método mas serve para marcar classes como serializáveis, sendo que diversas classes da api padrão do Java implementam a primeira.

No caso do Ruby, começamos criando um modelo chamado Comentario.

12.2 - Alterando o banco de dados

O conteúdo do script de migração criará as colunas "comentário", "id de quem tem o comentário", e o "tipo".

Nos campos id e tipo, colocamos o nome da coluna com o apelido seguido de _id e _type, respectivamente, notificando o Ruby que ele deve buscar tais dados daquilo que é "comentavel".

Note que no português a palavra "comentavel" soa estranho e parece esquisito trabalhar com ela, mas para seguir o padrão definido no inglês em diversas linguagens, tal apelido indica o que os modelos são capazes de fazer e, no caso, eles são "comentáveis".

O script deve então criar três colunas, sem nada de novo comparado com o que vimos até agora:

rails generate scaffold comentario conteudo:text \
        comentavel_id:integer comentavel_type

Caso seja necessário, podemos ainda adicionar índices físicos nas colunas do relacionamento, deixando a migration criada como a seguir:

class CreateComentarios < ActiveRecord::Migration
  def change
    create_table :comentarios do |t|
      t.text :conteudo
      t.integer :comentavel_id
      t.string :comentavel_type

      t.timestamps
    end

    add_index :comentarios, :comentavel_type
    add_index :comentarios, :comentavel_id
  end
end

Para trabalhar nos modelos, precisamos antes gerar a nova tabela necessária:

$ rake db:migrate

O modelo Comentario (app/models/comentario.rb) deve poder ser associado a qualquer objeto do grupo de modelos comentáveis. Qualquer objeto poderá fazer o papel de comentavel, por isso dizemos que a associação é polimórfica:

class Comentario < ActiveRecord::Base
  belongs_to :comentavel, polymorphic: true
end

A instrução :polymorphic indica a não existência de um modelo com o nome :comentavel.

Falta agora comentar que uma qualificação e um restaurante terão diversos comentários, fazendo o papel de algo comentavel. Para isso usaremos o relacionamento has_many:

class Qualificacao < ActiveRecord::Base
  # ...

  belongs_to :cliente
  belongs_to :restaurante

  has_many :comentarios, as: comentavel

  # ...
end

E o Restaurante:

class Restaurante < ActiveRecord::Base
  # ...

  has_many :qualificacoes
  has_many :comentarios, as: comentavel

  # ...
end

A tradução do texto pode ser quase literal: o modelo TEM MUITOS comentários COMO comentável.

Agora é a melhor hora de aprender algo novo

Se você gosta de estudar essa apostila aberta da Caelum, certamente vai gostar dos cursos online que lançamos na plataforma Alura. Você estuda a qualquer momento com a qualidade Caelum.

Conheça a Alura.

12.3 - Exercícios: Criando modelo de comentário

  1. Vamos criar o modelo do nosso comentário e fazer a migração para o banco de dados:

    1. Vá ao Terminal
    2. Digite:
      $ rails generate scaffold comentario conteudo:text
          comentavel_id:integer comentavel_type
      
      scaffold-comentario.png
    3. Vamos inserir alguns índices físicos. Abra o arquivo db/migrate/<timestamp>_create_comentarios.rb
    4. Insira as seguintes linhas:
      add_index :comentarios, :comentavel_type
      add_index :comentarios, :comentavel_id
      
      indice-comentario.png
    5. Volte ao Terminal e rode as migrações com o comando:
      $ rake db:migrate
      
  2. Vamos modificar nossos modelos:

    1. Abra o arquivo app/models/comentario.rb
    2. Adicione a seguinte linha:
      belongs_to :comentavel, polymorphic: true
      
      model-assoc-comentario.png
    3. Abra o arquivo app/models/qualificacao.rb
    4. Adicione a seguinte linha:
      has_many :comentarios, as: :comentavel
      
      model-assoc-qualificacao.png
    5. Abra o arquivo app/models/restaurante.rb
    6. Adicione a seguinte linha:
      has_many :comentarios, as: :comentavel
      
      model-assoc-restaurante.png
  3. Para o próximo capítulo, vamos precisar que o nosso sistema já inclua alguns comentários. Para criá-los, você pode usar o rails console ou ir em http://localhost:3000/comentarios e adicionar um comentário qualquer para o "Comentavel" 1, por exemplo, e o tipo "Restaurante". Isso criará um comentário para o restaurante de ID 1.