Pesquisar

Postagens populares

terça-feira, 27 de novembro de 2012

Conexão com múltiplas DBs no Ruby On Rails

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
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  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:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  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:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

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

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

database_externa_1:
  adapter: mysql2
  encoding: utf8
  database: test_db
  pool: 5
  username: root
  password: root
  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/",  tudo que precisamos agora é criar uma classe que herde a classe ActiveRecord::Base, por exemplo:

class ProjectDB < ActiveRecord::Base
    self.abstract_class = true
    establish_connection Rails.env.to_sym
end

class TestDB < ActiveRecord::Base
    self.abstract_class = true
    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:.  "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
      invoke  active_record
      create    db/migrate/20121127205353_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke    test_unit
      create      test/functional/users_controller_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      create        test/unit/helpers/users_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss
      invoke  scss
      create    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 < ActiveRecord::Base
    attr_accessible :email, :name
end

para:

class User < TestDB
    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
   ==  CreateUsers: migrating
   ====================================================
   -- create_table(:users)
      -> 0.0711s
   ==  CreateUsers: migrated (0.0712s)
   ===========================================

passando a conexão  "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
      invoke  active_record
      create    db/migrate/20121127212538_create_projects.rb
      create    app/models/project.rb
      invoke    test_unit
      create      test/unit/project_test.rb
      create      test/fixtures/projects.yml
      invoke  resource_route
       route    resources :projects
      invoke  scaffold_controller
      create    app/controllers/projects_controller.rb
      invoke    erb
      create      app/views/projects
      create      app/views/projects/index.html.erb
      create      app/views/projects/edit.html.erb
      create      app/views/projects/show.html.erb
      create      app/views/projects/new.html.erb
      create      app/views/projects/_form.html.erb
      invoke    test_unit
      create      test/functional/projects_controller_test.rb
      invoke    helper
      create      app/helpers/projects_helper.rb
      invoke      test_unit
      create        test/unit/helpers/projects_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/projects.js.coffee
      invoke    scss
      create      app/assets/stylesheets/projects.css.scss
      invoke  scss
      identical    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 < ActiveRecord::Base
    attr_accessible :description, :name, :user_id
end

para:

class Project < ProjectDB
    attr_accessible :description, :name, :user_id

    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 < TestDB
    attr_accessible :email, :name

    has_many :projects
end

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

rake db:migrate
   ==  CreateUsers: migrating
   ====================================================
   -- create_table(:users)
      -> 0.0014s
   ==  CreateUsers: migrated (0.0015s)
   ===========================================
   ==  CreateProjects: migrating
   =================================================
   -- create_table(:projects)
      -> 0.0014s
   ==  CreateProjects: migrated (0.0015s)
   ========================================

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 > User.create(:name => "AJ Alves", :email => "aj.alves@zerokol.com")
   (0.1ms)  BEGIN
  SQL (0.3ms)  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')
   (36.6ms)  COMMIT
 => #<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"> 
1.9.3p194 :002 > Project.create(:user_id => 1, :name => "Teste", :description => "Um projeto de teste")
   (0.0ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "projects" ("created_at", "description", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?)  [["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]]
   (117.6ms)  commit transaction
 => #<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:"> 
1.9.3p194 :003 > u = User.first
  User Load (0.4ms)  SELECT `users`.* FROM `users` LIMIT 1
 => #<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:"> 
1.9.3p194 :004 > u.projects
  Project Load (0.2ms)  SELECT "projects".* FROM "projects" WHERE "projects"."user_id" = 1
 => [#<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:">, #<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:">] 
1.9.3p194 :005 > p = Project.first
  Project Load (0.2ms)  SELECT "projects".* FROM "projects" LIMIT 1
 => #<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:"> 
1.9.3p194 :006 > p.user
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
 => #<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:"> 
1.9.3p194 :007 >

Nenhum comentário: