Usando Single Table Inheritance — STI com Active Record no Rails

É fato que trabalhar com o Active Record no Rails é algo trivial, e usar herança simples entre tabelas será que é simples também? É isso que veremos hoje. ;-)

A ideia de STI parte do princípio que podemos ter tabelas que compartilham dos mesmos dados mas que possuem ações diferences quando o tipo é diferente. Veja esse exemplo…

Car
===
color
model
Motorcycle
==========
color
model

Perceba que temos duas tabelas, uma Carro outra Moto. As duas possuem os mesmos campos, mas sabemos que o funcionamento será diference em cada um dos casos.

Por exemplo, em termos de “ações”, podemos “ligar” tanto a moto como o carro, mas sabemos que o carro pode abrir uma porta, já a moto não. Por outro lado, na moto podemos “empinar”, já no carro não.

Com isso entendemos que apesar dos mesmos dados serem armazenados, o funcionamento pode ser diferente em cada um dos tipos de veículo.

Sabendo disso, agora podemos usar o recurso de STI do Active Record de forma muito simples. Basta que criemos uma tabela genérica, que no nosso caso vai ser Veículo, adicionando a ela um campo extra to tipo string de nome type, o qual por convenção armazenará o tipo da tabela herdada. Veja:

rails g model Vehicle color:string model:string type:string

Assim podemos agora criar…

rails new sti_example
cd sti_example
rails g model Vehicle color:string model:string type:string
rails g model Car --skip-migration
rails g model Motorcycle --skip-migration
rails db:create db:migrate

Perceba que não geramos as migrations para os models Car e Motorcycle.

Agora que criamos os models, preciamos ajustar Car e Motorcycle para serem “herdeiros” do model Vehicle, caracterizando assim nossa STI. Veja:

class Car < Vehicle
end
class Motorcycle < Vehicle
end

Com essa configuração já podemos usar o rails console para verificar se a STI já funciona.

2.4.4 :001 > Car.new
=> #<Car id: nil, color: nil, model: nil, type: "Car", created_at: nil, updated_at: nil>
2.4.4 :002 > Motorcycle.new
=> #<Motorcycle id: nil, color: nil, model: nil, type: "Motorcycle", created_at: nil, updated_at: nil>

Prontinho. Verificamos acima que quando instanciamos os models eles agora já iniciam com o atributo type preenchido, cada um com seu tipo.

Nosso próximo passo é fazer um teste para ver se é possível executar funcionalidades distintas para cada um dos models. Vamos implementar um método de classe em cada um deles.

class Vehicle < ApplicationRecord
def self.turn_on
"Vrummm!!!"
end
end

class Car < Vehicle
def self.open_the_door
"Opppennninng the door!!!"
end
end

class Motorcycle < Vehicle
def self.wheelie
"Whooohoooo!!!"
end
end

Agora vamos fazer um teste no rails console, invocando os métodos a partir dos models para ver o funcionamento.

2.4.4 :001 > Car.turn_on
=> "Vrummm!!!"
2.4.4 :002 > Motorcycle.turn_on 
=> "Vrummm!!!"
2.4.4 :003 > Car.open_the_door
=> "Opppennninng the door!!!"
2.4.4 :004 > Motorcycle.wheelie
=> "Whooohoooo!!!"
2.4.4 :005 > Car.wheelie
NoMethodError: undefined method `wheelie' for Car (call 'Car.connection' to establish a connection):Class
Did you mean? where
from (irb):5
2.4.4 :006 > Motorcycle.open_the_door
NoMethodError: undefined method `open_the_door' for #<Class:0x00000000031e31a8>
from (irb):6

Conforme previsto, cada model só pode executar seu próprio método, além do método definido no “model pai”, nesse caso Vehicle. Fácil, não? :-)

É isso, gente! Aqui está o exemplo usado nesse post.

Ahh… se puderem, claro, inscrevam-se em nosso canal do youtube para receber as novidades, além também de curtir nossa página no Facebook , nos seguir nas redes sociais e cadastrar-se em nossa newsletter semanal.

Um super abraço e até a próxima! ;-)

Referência: https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html