Conexão com múltiplas DBs no Ruby On Rails
Autor: AJ O. Alves | Publicado: 2012-11-27T19:27:00-03:00

Que o framework Ruby on Rails é uma mão na roda para várias das situações do cotidiano dos desenvolvedores WEB não é segredo. No entanto algumas vez é necessário fazer algumas tarefas que não são tão comuns, como por exemplo conectar nossa aplicação em vários bancos de dados.


Imagem de www.askqtp.com

Então, vamos lá! Mostrarei a vocês como fazer o ActiveRecord (Classe para abstrair o nível de Banco de Dados) conectar-se a múltiplos banco de dados relacionais (Em um postagem futuro mostrarei como trabalhar com NoSQL), semelhantes, ou diversos dentre SQLite, MySQL, PostgreSQL, OracleSQL, SQL Server ...

Aplicação de Exemplo: Quero cria uma aplicação para gerenciar projetos. No entanto quero que a tabela de usuário fique em uma base da dados externa a aplicação para que outras aplicações futuras também utilizarem essa base de usuários unificada.

Passo 1: Configurar seu database.yml

O primeiro passo, é configurar seu config/database.yml para realizar suas conexões, por exemplo, temos este arquivo:

# SQLite version 3.x
#&nbsp;&nbsp; gem install sqlite3
#
#&nbsp;&nbsp; Ensure the SQLite 3 gem is defined in your Gemfile
#&nbsp;&nbsp; gem 'sqlite3'
development:
&nbsp; adapter: sqlite3
&nbsp; database: db/development.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
&nbsp; adapter: sqlite3
&nbsp; database: db/test.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000

production:
&nbsp; adapter: sqlite3
&nbsp; database: db/production.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000


Este é o padrão de criação com uma base no SQLite, por padrão configuradas com o nome dos ambientes de execução, vamos agora montar uma nova configuração, chamei ela de "database_externa_1" note que faremos conexão com um banco MySQL, o que é perfeitamente possível, já que o árduo trabalho de saber como lidar com cada tipo de base de dados é do ActiveRecord. Lembre também de referenciar as GENs dos adapters que estiver utilizando, neste exemplo "gem 'sqlite3'; gem 'mysql2';"

development:
&nbsp; adapter: sqlite3
&nbsp; database: db/development.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000

test:
&nbsp; adapter: sqlite3
&nbsp; database: db/test.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000

production:
&nbsp; adapter: sqlite3
&nbsp; database: db/production.sqlite3
&nbsp; pool: 5
&nbsp; timeout: 5000

database_externa_1:
&nbsp; adapter: mysql2
&nbsp; encoding: utf8
&nbsp; database: test_db
&nbsp; pool: 5
&nbsp; username: root
&nbsp; password: root
&nbsp; host: localhost


Passo 2: Criar uma Classe para manipular a conexão

O segundo passo é criar uma classe que servira para manipular a nova conexão, para isso, primeira vamos criar um arquivo chamado "my_databases.rb" dentro da pasta "config/initializers/",&nbsp; tudo que precisamos agora é criar uma classe que herde a classe ActiveRecord::Base, por exemplo:

class ProjectDB &lt; ActiveRecord::Base
&nbsp;&nbsp; &nbsp;self.abstract_class = true
&nbsp;&nbsp; &nbsp;establish_connection Rails.env.to_sym
end

class TestDB &lt; ActiveRecord::Base
&nbsp;&nbsp; &nbsp;self.abstract_class = true
&nbsp;&nbsp; &nbsp;establish_connection :database_externa_1
end


Muita atenção neste ponto!!! Porque eu criei duas classe?!
Basicamente por organização e simplicidade, para que cada modelo fique explicitamente vinculado a qual conexão ele pertence!

A primeira classe "ProjectdDB" faz referência as conexões padrões (:development, :test, :production) em SQLite. Note no valor que é passado ao método "establish_connection" que é Rails.env.to_sym.

A segunda classe "TestDB" faz referência a nossa base de dados externa em MySQL.
Obs:.&nbsp; "self.abstract_class = true" significa dizer que a classe é abstrata, ou seja, não pode ser instanciada só herdada.

Neste ponto já possuímos toda a nossa aplicação configurada para trabalhar com base de dados distintas. Agora mostrarei através de exemplos como devemos trabalhar.

Exemplo 1: Criar a tabela de usuários.

rails generate scaffold user name:string email:string
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; active_record
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; db/migrate/20121127205353_create_users.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; app/models/user.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/unit/user_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/fixtures/users.yml
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; resource_route
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route&nbsp;&nbsp;&nbsp; resources :users
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; scaffold_controller
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; app/controllers/users_controller.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users/index.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users/edit.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users/show.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users/new.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/users/_form.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/functional/users_controller_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; helper
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/helpers/users_helper.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/unit/helpers/users_helper_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; assets
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; coffee
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/assets/javascripts/users.js.coffee
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/assets/stylesheets/users.css.scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; app/assets/stylesheets/scaffolds.css.scss


Como a tabela de usuários deve ficar na base externa devemos tomar alguns procedimentos:

1º - Alterar o arquivo app/models/user.rb

class User &lt; ActiveRecord::Base
&nbsp;&nbsp;&nbsp; attr_accessible :email, :name
end


para: 

class User &lt; TestDB
&nbsp;&nbsp;&nbsp; attr_accessible :email, :name
end


substituir o ActiveRecord::Base por sua subclasse TestDB, assim, a aplicação se onde encontrar a tabela users que será criada.

2º - Radar a migração de forma apropriada

rake db:migrate RAILS_ENV=database_externa_1
&nbsp;&nbsp; ==&nbsp; CreateUsers: migrating
&nbsp;&nbsp; ====================================================
&nbsp;&nbsp; -- create_table(:users)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; 0.0711s
&nbsp;&nbsp; ==&nbsp; CreateUsers: migrated (0.0712s)
&nbsp;&nbsp; ===========================================


passando a conexão&nbsp; "database_externa_1" como parâmetro, assim, a tabela será criada no banco externo.

Exemplo 2: Criar a tabela de projetos.

rails generate scaffold project user_id:integer name:string description:text
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; active_record
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; db/migrate/20121127212538_create_projects.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; app/models/project.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/unit/project_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/fixtures/projects.yml
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; resource_route
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route&nbsp;&nbsp;&nbsp; resources :projects
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; scaffold_controller
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp; app/controllers/projects_controller.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects/index.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects/edit.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects/show.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects/new.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/views/projects/_form.html.erb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/functional/projects_controller_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; helper
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/helpers/projects_helper.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test_unit
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test/unit/helpers/projects_helper_test.rb
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; assets
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; coffee
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/assets/javascripts/projects.js.coffee
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp;&nbsp;&nbsp; scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; create&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app/assets/stylesheets/projects.css.scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; invoke&nbsp; scss
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; identical&nbsp;&nbsp;&nbsp; app/assets/stylesheets/scaffolds.css.scss


Como a tabela de projetos deve ficar na base local:

1º - Alterar o arquivo app/models/project.rb

class Project &lt; ActiveRecord::Base
&nbsp;&nbsp; &nbsp;attr_accessible :description, :name, :user_id
end


para: 

class Project &lt; ProjectDB
&nbsp;&nbsp; &nbsp;attr_accessible :description, :name, :user_id

&nbsp;&nbsp; &nbsp;belongs_to :user
end


substituir o ActiveRecord::Base por sua subclasse ProjectDB. Note que toda as relações entre os modelos podem ser construídas normalmente, para a aplicação não faz mais diferença a sua localidade ou tipo. Então configure o modelo "user" também:

class User &lt; TestDB
&nbsp;&nbsp;&nbsp; attr_accessible :email, :name

&nbsp;&nbsp;&nbsp; has_many :projects
end


2º - Radar a migração de forma apropriada

rake db:migrate
&nbsp;&nbsp; ==&nbsp; CreateUsers: migrating
&nbsp;&nbsp; ====================================================
&nbsp;&nbsp; -- create_table(:users)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; 0.0014s
&nbsp;&nbsp; ==&nbsp; CreateUsers: migrated (0.0015s)
&nbsp;&nbsp; ===========================================
&nbsp;&nbsp; ==&nbsp; CreateProjects: migrating
&nbsp;&nbsp; =================================================
&nbsp;&nbsp; -- create_table(:projects)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; 0.0014s
&nbsp;&nbsp; ==&nbsp; CreateProjects: migrated (0.0015s)
&nbsp;&nbsp; ========================================


Veja que não a necessidade de passar a conexão com o parâmetro, pois será utilizada a padrão do ambiente.
Agora, todos os modelos do seu projeto devem herdar de ProjecDB e não mais de ActiveRecord::Base.
Pronto, isso é tudo o que você deve saber para trabalhar com múltiplas base de dados relacionais em seu projeto Ruby on Rails.

Para testar, vá ao terminal:

rails c
Loading development environment (Rails 3.2.8)
1.9.3p194 :001 &gt; User.create(:name =&gt; "AJ Alves", :email =&gt; "aj.alves@zerokol.com")
&nbsp;&nbsp; (0.1ms)&nbsp; BEGIN
&nbsp; SQL (0.3ms)&nbsp; INSERT INTO `users` (`created_at`, `email`, `name`, `updated_at`) VALUES ('2012-11-27 21:37:28', 'aj.alves@zerokol.com', 'AJ Alves', '2012-11-27 21:37:28')
&nbsp;&nbsp; (36.6ms)&nbsp; COMMIT
&nbsp;=&gt; #&lt;user 21:37:28="21:37:28" 2="2" aj.alves="aj.alves" alves="alves" created_at:="created_at:" email:="email:" id:="id:" name:="name:" updated_at:="updated_at:" zerokol.com="zerokol.com"&gt; 
1.9.3p194 :002 &gt; Project.create(:user_id =&gt; 1, :name =&gt; "Teste", :description =&gt; "Um projeto de teste")
&nbsp;&nbsp; (0.0ms)&nbsp; begin transaction
&nbsp; SQL (0.4ms)&nbsp; INSERT INTO "projects" ("created_at", "description", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?)&nbsp; [["created_at", Tue, 27 Nov 2012 21:38:16 UTC +00:00], ["description", "Um projeto de teste"], ["name", "Teste"], ["updated_at", Tue, 27 Nov 2012 21:38:16 UTC +00:00], ["user_id", 1]]
&nbsp;&nbsp; (117.6ms)&nbsp; commit transaction
&nbsp;=&gt; #&lt;project 1="1" 21:38:16="21:38:16" 2="2" created_at:="created_at:" de="de" description:="description:" este="este" id:="id:" m="m" name:="name:" projeto="projeto" teste="teste" updated_at:="updated_at:" user_id:="user_id:"&gt; 
1.9.3p194 :003 &gt; u = User.first
&nbsp; User Load (0.4ms)&nbsp; SELECT `users`.* FROM `users` LIMIT 1
&nbsp;=&gt; #&lt;user 1="1" 21:33:42="21:33:42" aj.zerokol="aj.zerokol" alves="alves" created_at:="created_at:" email:="email:" gmail.com="gmail.com" id:="id:" name:="name:" updated_at:="updated_at:"&gt; 
1.9.3p194 :004 &gt; u.projects
&nbsp; Project Load (0.2ms)&nbsp; SELECT "projects".* FROM "projects" WHERE "projects"."user_id" = 1
&nbsp;=&gt; [#&lt;project 1="1" 21:33:58="21:33:58" created_at:="created_at:" d="d" description:="description:" dsfsdf="dsfsdf" este="este" id:="id:" name:="name:" sdf="sdf" sdfs="sdfs" updated_at:="updated_at:" user_id:="user_id:"&gt;, #&lt;project 1="1" 21:38:16="21:38:16" 2="2" created_at:="created_at:" de="de" description:="description:" este="este" id:="id:" m="m" name:="name:" projeto="projeto" teste="teste" updated_at:="updated_at:" user_id:="user_id:"&gt;] 
1.9.3p194 :005 &gt; p = Project.first
&nbsp; Project Load (0.2ms)&nbsp; SELECT "projects".* FROM "projects" LIMIT 1
&nbsp;=&gt; #&lt;project 1="1" 21:33:58="21:33:58" created_at:="created_at:" d="d" description:="description:" dsfsdf="dsfsdf" este="este" id:="id:" name:="name:" sdf="sdf" sdfs="sdfs" updated_at:="updated_at:" user_id:="user_id:"&gt; 
1.9.3p194 :006 &gt; p.user
&nbsp; User Load (0.4ms)&nbsp; SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
&nbsp;=&gt; #&lt;user 1="1" 21:33:42="21:33:42" aj.zerokol="aj.zerokol" alves="alves" created_at:="created_at:" email:="email:" gmail.com="gmail.com" id:="id:" name:="name:" updated_at:="updated_at:"&gt; 
1.9.3p194 :007 &gt;