Bancos de Dados Legados com Rails

Uma das dúvidas mais comuns que praticamente todos os novos alunos perguntam, é: “Se o Rails gera as migrations para criar o banco de dados, como vou usá-lo se já tenho o banco de dados pronto que é usado em uma aplicação antiga, por exemplo?

A pesar de parecer algo complicado, a resposta para isso é simples. O Rails permite que todas as configurações feitas através da convenção possam ser reconfiguradas/sobrescritas, e é isso que vamos mostrar hoje.

Quando estamos trabalhando com BD no Rails precisamos ter em mente as 3 principais coisas em que ele atua quando falamos em Convenção sobre Configuração.

1- O nome das tabelas sempre é o nome do model no plural.

2- Toda tabela tem uma chave primária chamada ID.

3- As associações partem do princípio de que o nome do campo que faz a FK é sempre o nome do model com um “underline ID” no final.

Veja esse nosso exemplo padrão:

contacts      # nome da tabela no BD
========
id # chave primária (PK)
name
kind_id # chave estrangeira (FK) para a tabela kinds
kinds         # nome da tabela no BD
=====
id # chave primária (PK)
description

Perceba que o Rails geraria uma tabela contacts (no plural), com uma chave primária (id) e uma chave estrangeira (kind_id) que é a associação entre contacts e a tabela kinds. Ou seja, esse é o padrão que o Rails usa.

Agora imagine se essas tabelas forem de um sistema legado, já usado na empresa a muito tempo e que os nomes envolvidos não são padronizados conforme o Rails. Veja esse exemplo:

tab_contact     # nome da tabela no BD
===========
contact_id # chave primária (PK)
txt_name
kind # chave estrangeira (FK) para a tabela kinds
tab_kind        # nome da tabela no BD
========
kind_id # chave primária (PK)
txt_description

Acima temos as mesmas duas tabelas, mas dessa vez de forma não padronizada pelo Rails. Percebemos que:

  • O nome das tabelas não seguem o padrão Rails de serem apenas o nome da tabela no plural.
  • As chaves primárias não são mais o nome ID.
  • A associação também não segue mais a padronização que usava um “_id” no final.

Sendo assim, preparei uma app que possui um banco de dados SQLite com as duas tabelas acima para usarmos de exemplo. Você pode baixá-la aqui (https://github.com/jacksonpires/legacy-db-example).

Nesse momento, após fazer o clone do projeto, você pode através do rails dbconsole comprovar que as tabelas estão fora do padrão Rails, usando o .schema tab_contact.schema tab_kind . Veja:

rails dbconsole
SQLite version 3.8.7.1 2014-10-29 13:59:56
Enter ".help" for usage hints.
sqlite> .schema tab_contact
CREATE TABLE `tab_contact` (
`contact_id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
`txt_name` TEXT,
`kind` INTEGER

);
sqlite> .schema tab_kind
CREATE TABLE `tab_kind` (
`kind_id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
`txt_description` TEXT

);
sqlite>

Para ajustar cada um dos models para trabalhar com as tabelas existentes, fiz as seguintes alterações:

class Contact < ApplicationRecord
self.table_name = "tab_contact"
self.primary_key = "contact_id"
belongs_to :kind, :foreign_key => "kind",
:primary_key => "kind_id"

end
class Kind < ApplicationRecord
self.table_name = "tab_kind"
self.primary_key = "kind_id"
end

Resumindo, alteramos o nome da tabela para qual model Contact vai se referir, bem como a chave primária e também indicamos como se dará o relacionamento belongs_to indicando a chave estrangeira e a chave primária da outra tabela. Já no model Kind informamos o nome da tabela e o nome da chave primária usada no banco de dados.

Prontinho. Com essa pequena configuração sobrescrevemos a convenção que o Rails usaria no acesso às tabelas. Vamos fazer um teste.

# Para verificar se o model já reconhece as tabelas podemos
# simplesmente rodar um <Model>.new
# -------------------------------------------------------
Contact.new
=> #<Contact contact_id: nil, txt_name: nil, kind: nil>
Kind.new
=> #<Kind kind_id: nil, txt_description: nil>
# Agora vamos criar dois registros para testar os models
# -------------------------------------------------------
Kind.create!(txt_description: "Amigo")
=> #<Kind kind_id: 1, txt_description: "Amigo">
Contact.create!(txt_name: "Jackson", kind: Kind.first)
=> #<Contact contact_id: 1, txt_name: "Jackson", kind: 1>

Simples, não? :-)

Além disso, outro ajuste que fiz foi “remapear” os nomes dos campos para o Rails usando o método alias_attribute. Veja

class Contact < ApplicationRecord
self.table_name = "tab_contact"
self.primary_key = "contact_id"
belongs_to :kind, :foreign_key => "kind", :primary_key => "kind_id"
 alias_attribute :name, :txt_name
end
class Kind < ApplicationRecord
self.table_name = "tab_kind"
self.primary_key = "kind_id"
  alias_attribute :description, :txt_description
end

Prontinho. Agora podemos usar dessa forma:

Kind.create!(description: "Amigo")
=> #<Kind kind_id: 2, txt_description: "Amigo">
Contact.create!(name: "Jackson", kind: Kind.first)
=> #<Contact contact_id: 2, txt_name: "Jackson", kind: 1>

Observe que mesmo usando description e name ele mapeou para txt_name e txt_description, como esperávamos. Super fácil, hein!?

Então é isso! Acredito que agora você vai conseguir configurar o Rails para qualquer banco de dados legado. ;-)

Se quiser receber mais novidades como essa basta assinar nossa newsletter, e se quiser dar aquela força ao nosso projeto é só curtir nossa página no Facebook e nos seguir nas redes sociais.

É isso, pessoal! Até a próxima!