Porque nomear um atributo como “type” não é uma boa ideia em Rails.

Ou: como utilizar o padrão Herança de Tabela Única (Single Table Inheritance) em Rails.

Se alguma vez você já tentou criar um atributo chamado “type” no seu modelo Active Record, provavelmente você teve algum problema, ou já sabia exatamente o que estava fazendo. Se você se encaixa no primeiro grupo, então esse post talvez possa lhe ajudar um pouco.

Um pequeno exemplo

Vamos criar um scaffold usando Rails da seguinte maneira:

rails generate scaffold Dealer name type company

Criamos o Dealer (Negociante) que no nosso dominio tem os atributos nome (nome da pessoa) e empresa (empregador). Adicionamos também um campo type, esse campo deve ter os valores “Seller” caso seja um vendedor ou “Buyer” caso seja um comprador.

Agora vamos no rails console para criar um novo Dealer da seguinte maneira:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"

A seguinte exceção será lançada: ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Seller is not a subclass of Dealer.

O começo da mensagem diz que o tipo da herança de tabela única é invalido pois a classe Seller não é uma subclasse de Dealer. Vamos ver um pouco mais sobre o padrão Single Table Inheritance (STI) [1]

Single Table Inheritance

O padrão STI está catalogado como parte dos Padrões de Arquitetura de Aplicações Empresariais (Patterns of Enterprise Application Architecture). O cenário de aplicação do padrão é quando você precisa representar uma herança em uma base de dados relacional.

A ideia para resolver o problema é bem simples, vamos adicionar um campo que vai dizer qual subclasse deve ser instanciada com aqueles dados. Voltando ao exemplo do post, podemos então ter uma superclasse chamada Dealer e duas subclasses Seller/Buyer que vão ter os mesmos atributos, mas lógicas diferentes.

O Rails já vem pronto para utilizarmos o STI. Basta utilizar que chamemos o campo que diferencia a classe de ‘type’. Por isso, no exemplo anterior, aquele exceção foi lançada. Criamos o modelo Dealer, mas ainda não temos o modelo Seller ou Buyer.

Para resolver o problema vamos criar o Seller e Buyer e fazer com que elas herdem de Dealer. Note que as classes Seller e Buyer não precisam herdar da classe ActiveRecord pois elas vão herdar de Dealer. O código seria algo do tipo:

class Seller < Dealer
end

Agora, se tentarmos criar o mesmo objeto de antes, veremos a seguinte saida:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"
=> #<Seller id: 1, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:51:55", updated_at: "2013-11-14 00:51:55">

Ou seja, mesmo criando um Dealer, o Rails sabe que se trata de um Seller, graças ao STI. Se tentarmos criar um Seller também conseguimos o mesmo resultado:

Seller.create name: "Zeca", company: "Uburu Inc"
=> #<Seller id: 2, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:57:36", updated_at: "2013-11-14 00:57:36">

Veja também que o Rails já colocou o valor do atributo ‘type’ para “Seller” automaticamente. Assim você não precisa se preocupar em sempre criar um “Dealer” com o ‘type’ sendo “Seller” ou “Buyer”, basta utilizar a classes que você realmente precisa.

Se o nosso objetivo fosse só ter um campo chamado ‘type’ mas sem nenhuma herança podemos simplesmente renomear o campo para ‘alguma_coisa_type’ ou, se isso não for possível, podemos adicionar a seguinte linha na implementação da classe:

class Seller < ActiveRecord::Base
  self.inheritance_column = nil
end

Assim, o Rails não vai mais se importar com o ‘type’ para herança. Ou caso você precise configurar o atributo que define a herança como outro nome, também é possível passar o nome desejado para que o Rails trate o modelo como uma implementação do STI.

Referências

1. Martin Fowler, Single Table Inheritance. http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html

2 comentários sobre “Porque nomear um atributo como “type” não é uma boa ideia em Rails.

  1. Ae Briza! Show o post, mas tem um “typo” no código que está criando o Seller. Tu esqueceu de tirar o type: “seller” dali. Valeu, parabéns!

Deixe um comentário