Connecting to multiple databases in ruby on rails - ruby-on-rails

I have a ruby on rails application working fine and connected to a database. Now i want to connect to a different database from the same application. The data model can be exactly the same. In fact if i connect to the different database the application works fine. However I want to connect to two different databases. Is it possible in ruby on rails?

For multiple database connection, you need to add the following codes to the database.yml file. Here, I am giving the example of connecting two databases from a rails application
config/database.yml
development:
adapter: mysql2
database: db1_dev
username: root
password: xyz
host: localhost
development_sec:
adapter: mysql2
database: db2_dev
username: root
password: xyz
host: localhost
production:
adapter: mysql2
database: db1_prod
username: root
password: xyz
host: your-production-ip
production_sec:
adapter: mysql2
database: db2_prod
username: root
password: xyz
host: your-production-ip
Here I have used two databases for the development and production environment.
Now we need to connect the model to databases. When you are running your application in development and production mode, all the models will be mapped through the development and production db parameters those been mentioned in your database.yml. So for some model we need to connect to other database.
Lets assume that, we have two models User and Category. The users table is in db1_dev and db1_prod, the categories table in db2_dev and db2_prod.
Category model
class Category < ActiveRecord::Base
establish_connection "#{Rails.env}_sec".to_sym
end
Similarly, when you adding the new migration for the second database, need to add following code to it.
class CreateRewards < ActiveRecord::Migration
def connection
ActiveRecord::Base.establish_connection("#{Rails.env}_sec".to_sym).connection
end
def change
# your code goes here.
end
end
Hope it will work for you :) .

Use establish_connection to switch to a different database:
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "myuser",
:password => "mypass",
:database => "somedatabase"
)
You can also pass a preconfigured environment from database.yml like so:
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['other_env'])
You can also set it for a specific model:
MyClass.establish_connection(...)

You might like to note that as of Rails 6 (2019), Rails has support for multiple primary databases!
https://guides.rubyonrails.org/active_record_multiple_databases.html
The database.yml file will now look something like this:
development:
primary:
database: primary_db
user: root
primary_replica:
database: primary_db
user: ro_user
replica: true
animals:
database: my_animals_db
user: root
migrations_path: db/animals_migrate
animals_replica:
database: my_animals_db
user: ro_user
replica: true
And then it's as simple as specifying in your model files:
class AnimalsModel < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :animals_primary, reading: :animals_replica }
end
class Dog < AnimalsModel
# connected to both the animals_primary db for writing and the animals_replica for reading
end
(These examples were taken from this helpful tutorial.)

Related

How do I pull data from a dual database in Rails (Postgresql and SQL Server)

I was working with just one database initially but I needed to add the clients other database which is a SQL Server database. I was able to connect but I am running into a few problems.
Original full database.yml
development:
adapter: postgresql
database: martin_development
username: *******
password: *******
pool: 5
timeout: 5000
development_sec:
adapter: sqlserver
host: *******
port: 1433
database: Database1
username: *******
password: *******
I get the following error:
TinyTds::Error (Database 'DATABASE1' does not exist. Make sure that the name is entered correctly.)
If I take out the database name so it looks like this:
development_sec:
adapter: sqlserver
host: *******
port: 1433
database:
username: *******
password: *******
Everything appears to run normal and I do not get any error messages in regards to a database not being found. However, that database does exist on the clients side.
I am confused on how to extract data from my customers SQL Server database. I am trying to follow along with some resources I found so my project files so far are looking like this:
Model (Mssql.rb)
class MssqlBase < ActiveRecord::Base
establish_connection :development_sec
self.abstract_class = true
end
Model(site.rb)
class Site < MssqlBase
end
Controller(sites_controller.rb)
class SitesController < ApplicationController
def index
#sites = Site.all
#hash = Gmaps4rails.build_markers(#sites) do |site, marker|
marker.lat site.latitude
marker.lng site.longitude
end
end
end
database.yml
development:
adapter: postgresql
database: martin_development
username: *******
password: *******
pool: 5
timeout: 5000
development_sec:
adapter: sqlserver
host: *******
port: 1433
database:
username: *******
password: *******
My goal is to start pulling data from the database but as of right now with this look in regards to my file I am getting the following error:
ActiveRecord::StatementInvalid (TinyTds::Error: Invalid object name 'sites'.: SELECT [sites].* FROM [sites]):
So just to recap I need it to read from the 'DATABASE1' but it is saying it does not exist. When I leave it out for some reason it is connecting with the server but I do not know which database. Now I am trying to pull the data but am getting invalid statements. Any help would be appreciated.
You can do this with Rails.
Create this method.
def with_connection(database, &block)
ActiveRecord::Base.establish_connection(database)
yield
ActiveRecord::Base.establish_connection
end
It changes what database your app is connected to, runs the query and then restores it to default. Not sure if thread safe. Using establish_connection on its own, will not revert to original (as your app can only be connected to one database at a time)
Then use:
with_connection :development_sec do
MssqlBase.first # will call development_sec database
end

using activerecord with nested databases

I am building a rails app to store many different, user-supplied database connections and execute arbitrary sql against each of them. I am representing each database connection string as an instance of a 'connection' model and want to be able to write a method to query the database represented by each connection, ideally using the activerecord ORM. However, my code as written overwrites the database connection for the entire connections table when I use the establish_connection method in the following code. How would you advise me to change the code to query an arbitrary database, without overwriting the connection for the whole Connections table?
class Connection < ActiveRecord::Base
validates_presence_of :host, :port, :db_name, :db_user, :db_password, :db_type
def connect
self.connection = ActiveRecord::Base.establish_connection(
adapter: self.db_type,
host: self.host,
database: self.db_name,
username: self.db_user,
password: self.db_password
)
end
end
A good way to do that is making a model for each database connection you need, and then make other models as subclasses of them. So, for example:
Define all the needed connections in database.yml (per-environment)
# DB 1
development:
adapter: mysql2
encoding: utf8
database: db_1
username: ****
password: ****
host: ********
pool: 5
...
# DB 2
db2_development:
adapter: mysql2
encoding: utf8
database: db_2
username: ****
password: ****
host: ********
pool: 5
...
# Same for production (and/or other environments)
production:
...
db2_production:
...
Define a "master" model for each database, which inherits from ActiveRecord::Base
# DB1
class DB1 < ActiveRecord::Base
self.abstract_class = true
end
# DB2
class DB2 < ActiveRecord::Base
self.abstract_class = true
establish_connection "db2_#{Rails.env}"
end
...
Now define all the database-specific models as subclasses of the models defined above, in this way:
# DB1 specific model
class DB1_model < DB1
# model logic here
end
# DB2 specific model
class DB2_model < DB2
# model logic here
end
...
And you're good to go.
In this way, you can connect to N databases in different environments (usually development, staging, preprod and production, but they may be different in your case).
Also, remeber that Rails will manage a pool of SQL connections for each database.
In the example above, Rails will open a maximum of 5 connections for each database, so total will be 10 (for a single instance of your application).
If you use Phusion Passenger or Unicorn, and spawn 8 application instances, total SQL connections will be (at maximum) 10*8 = 80.

How to configure multiple database sources in a rails application

I would like to configure 2 database instances for a certain environment (say staging or production). The default rails new application just provides a single database instance how do I configure 2 database instances.
You can copy and past one of your existing configurations such as development. and rename it as you want so, in the database.yml:
development:
adapter: mysql2
encoding: utf8
reconnect: false
database:
pool: 5
username: root
password:
host: localhost
new_database:
adapter: mysql2
encoding: utf8
reconnect: false
database:
pool: 5
username: root
password:
host: localhost
Then in the models you create for this new connection add the following methods to the model, for example:
class Document < ActiveRecord::Base
self.table_name = "document" # this allows you to hide a non comforming table name behind the rails model it is NOT necessary for establish_connection to work
self.establish_connection "new_database" # notice there is no = when setting this value, strange, I know.
end

how to trigger a rake:db:prepare that it also migrates an external database connection

I'm currently trying to perform some tests on a rails app that has two database connections.
As of course I wouldn't like to delete the 2nd database each time I perform a test, I have set up a mechanism to connect to another 2nd-DB if the environment is "test" (see below).
My question: How could I tell rake test to also perform a rake db:test:prepare on the 2nd DB?
This query already touched my problem but couldn't help me completely.
Some code to explain:
My ActiveRecord-Classes that connect to the 2nd db inherit from the following class InformixConnect:
class InformixConnect < ActiveRecord::Base
self.abstract_class = true
case Rails.env
when 'production', 'development'
establish_connection :development_informix
when 'test'
establish_connection :test_informix_dummy
else
raise "Please specify a correct informix Environment."
end
end
like
class RenewalNotify < InformixConnect
set_table_name :renewal_notify
set_primary_key :renewal_notify_id
end
(yes, I know... The schema does not follow the rails conventions. It's a legacy one)
database.yml contains
...
development:
adapter: postgresql
database: rails_development
host: 127.0.0.1
reconnect: true
username: rails
password: guessone
...
development_informix:
adapter: informix
database: SOMETHING
username: yyy
password: yyy
test_informix_dummy:
adapter: sqlite3
database: db/test_informix_dummy.sqlite3
...
# 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: &TEST
adapter: sqlite3
database: db/test.db
verbosity: silent
timeout: 5000
So I'd need to setup the "test_informix_dummy" db each time I perform a rake test

Rails append database

Is it possible to append two databases in rails application? For example, SQLite database is portable. I can download SQLite database from another server. When rails application starts, it mount the database. Can i append all the data from another database to existing database? May be SQLite provide a way for merging databases?
I'm not sure sure what did you mean saying "append databases in application". But you can use 2 different (with different schemes) databases in your application. For example:
config/database.yml
development:
adapter: mysql2
encoding: utf8
reconnect: false
database: project_dev
pool: 5
username: project
password:
test:
adapter: mysql2
encoding: utf8
reconnect: false
database: project_test
pool: 5
username: project
password:
sqlite:
development:
adapter: sqlite3
database: db/development.project.db
pool: 5
timeout: 5000
test:
adapter: sqlite3
database: db/test.project.db
pool: 5
timeout: 5000
Models:
Abstract model for all sqlite models;
uses connection ninja gem
class SqliteModel < ActiveRecord::Base
self.abstract_class = true
use_connection_ninja(:sqlite)
end
Sqlite model
class Book < SqliteModel
set_table_name :Books
set_primary_key :BookID
belongs_to :volume, :foreign_key => :VolumeID
has_many :chapters, :foreign_key => :BookID
end
Mysql model
class Highlight < ActiveRecord::Base
# ...
end
You can even use associations between tables in different databases.
But if you was asking about using 2 databases with the same scheme(i.e. just different data), then my answer is no, it's not possible(I can be wrong though). I think that is a question about replication, synchronization, backups or something similar - DB layer, not application.
Of course you can have 2 same tables in both databases, 2 models - one per database, and then just copy records from one to another. But Rails won't do it automatically.
If you want to "seed" your database with data from an external source, like a central location, your best bet is to:
Provide such data into a easy to iterate format (like CSV, JSON or YAML);
Merge this data into your database on your app's initialization, providing it does not exist.
I do something like this on some projects. I would not, however, perform the merge automatically: I'd rather use the db/seeds.rb file and the rake db:seed task:
if State.count == 0
State.transaction do
CSV.foreach("#{::Rails.root}/db/seed_data/states.csv", 'r') do |row|
code, acronym, name = *row
State.create! code: code, acronym: acronym, nane: name
end
end
end
In this code I load the data from a local file, but you can easily change to a remote file using Net::HTTP.

Resources