Different DB connection for individual model depending on environment - ruby-on-rails

I have a Model (MessageImporter) that connects to a different Database than the other models using self.establish_connection. Everything works fine when I pass the connection info hardcoded. Now I need the connection to depend on the current environment. So I added the info to my application_config.yml (it's a simple nifty_config). I got stuck at how to pass the connection info to self.establish_connection.
Here's my current code:
class MessageImporter < ActiveRecord::Base
self.establish_connection lambda {{
:adapter => APP_CONFIG[:external_messages][:adapter],
:host => APP_CONFIG[:external_messages][:host],
:database => APP_CONFIG[:external_messages][:database],
:username => APP_CONFIG[:external_messages][:username],
:password => APP_CONFIG[:external_messages][:password]
}}
# […]
I get an undefined method 'symbolize_keys' for #<Proc:0x2564224>-. Error I also tried it without the lambda, but that does not work either and I get an even weirder error:
Please install the adapter: `gem install activerecord--adapter` (no such file to load -- active_record/connection_adapters/_adapter)
Or is there a better/Rails-ish way to set a different db-connection for individual models?

Little different approach and it works:
Adding the connection info to the regular database.yml:
development:
adapter: mysql
encoding: utf8
reconnect: false
database: […]
pool: 5
username: root
password:
socket: /tmp/mysql.sock
message_import:
adapter: mysql
encoding: utf8
username: […]
password: […]
database: […]
host: […]
Note the nesting! message_import ist nested beneath development.
Within the message_importer.rb:
class MessageImporter < ActiveRecord::Base
establish_connection configurations[RAILS_ENV]['message_import']
Still wondering why my first approach didn't work but this does just as expected.

According to the documentation, the establish_connection method accepts a hash as input. Did you try this?—
class MessageImporter < ActiveRecord::Base
establish_connection {
:adapter => APP_CONFIG[:external_messages][:adapter],
:host => APP_CONFIG[:external_messages][:host],
:database => APP_CONFIG[:external_messages][:database],
:username => APP_CONFIG[:external_messages][:username],
:password => APP_CONFIG[:external_messages][:password]
}

Related

How to connect to primary database and shards using Rails 6.1 multiple databases ActiveRecord::ConnectionHandling?

I am trying to build an app which will use one of latest Rails 6.1 features: multiple databases - you can read in sources at the bottom.
I would like to implement multitenancy by being able to switch between databases based on request domain.
Each club would have his own database, and one primary database would store app specific data like Users, Clubs.
I created two types of records: GlobalRecord using primary database and ShardRecord using horizontal shards databases.
And also tried to use around_action to select current club database.
# config/database.yml
default: &default
adapter: postgresql
encoding: unicode
development:
primary:
<<: *default
database: primary
migrations_paths: db/migrate
club_1:
<<: *default
database: club_1
migrations_paths: db/shard_migrate
club_2:
<<: *default
database: club_2
host: 1.1.1.1
username: deploy
password: pass
migrations_paths: db/shard_migrate
class Club < GlobalRecord; end
class GlobalRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
club_1: { writing: :primary, reading: :primary },
club_2: { writing: :primary, reading: :primary }
}
end
class MemberRecord < ShardRecord; end
class ShardRecord < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
club_1: { writing: :club_1, reading: :club_1 },
club_2: { writing: :club_2, reading: :club_2 }
}
end
class ApplicationController < ActionController::API
before_action :set_club
around_action :connect_to_shard
private
def set_club
#club = SelectClubByDomain.new(request).slug
end
def connect_to_shard
ActiveRecord::Base.connected_to(role: :writing, shard: #club.slug) do
yield
end
end
end
I have few design questions/issues I would like to ask YOU about:
I would like to set GlobalRecord with connects_to database: { writing: :primary } but because I added around_action
I have to set it as above.
Is it bad design?
Can I refactor it so I could always call GlobalRecord without ActiveRecord::Base.connected_to(role: :writing, shard: :club_1) block?
Tried to use ActiveRecord::Base.connected_to_many(GlobalRecord, ShardRecord, role: :writing, shard: #club_slug.to_sym) in around_action but on request(clubs#index => [Club.all]) I get an error:
ActiveRecord::ConnectionNotEstablished (No connection pool for 'GlobalRecord' found for the 'club_1' shard.)
On production server on start I see this warning, how to fix it?
=> Run `bin/rails server --help` for more startup options
Failed to define attribute methods because of ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
Sources:
https://guides.rubyonrails.org/active_record_multiple_databases.html
https://www.freshworks.com/horizontal-sharding-in-a-multi-tenant-app-with-rails-61-blog/
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionHandling.html#method-i-connected_to_many
In general it's absolutely fine to switch connections with an around filter, we have been doing this for years (even before Rails 6) in my previous company.
Btw: did you see that the guide is mentioning your posted exception?
Note that connected_to with a role will look up an existing connection and switch using the connection specification name. This means that if you pass an unknown role like connected_to(role: :nonexistent) you will get an error that says ActiveRecord::ConnectionNotEstablished (No connection pool for 'ActiveRecord::Base' found for the 'nonexistent' role.)
https://guides.rubyonrails.org/active_record_multiple_databases.html#using-manual-connection-switching

Connecting to multiple databases in 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.)

rails and sqlserver encoding

I'm developing an aplication with mysql and sqlserver. sqlserver is used to connect to a legacy table.
here is my definition
development:
adapter: mysql2
encoding: utf8
reconnect: false
database: spvalores_development
username: secret
password: secret
pool: 5
host: 192.168.100.35
socket: mysql
port: 3306
sql_server_db:
adapter: sqlserver
database: SPPokerManager
dsn: DesaSqlServer
mode: odbc
username: sa
password: master
encoding: utf8
the gems i use:
gem 'activerecord-sqlserver-adapter'
gem 'ruby-odbc'
this is my model
class Client < ActiveRecord::Base
self.table_name = "dbo.LOB_CLIENTE"
self.primary_keys = :CASINO_ID, :CLIENTE_ID
establish_connection :sql_server_db
attr_accessible :CASINO_ID, :CLIENTE_ID, :CDNI, :CAPELLIDO, :CNOMBRES,:TIPODOCUMENTO_ID, :CNICKNAME, :CDIRECCION, :CTELEFONO, :CCELULAR, :CEMAIL, :COBSERVACIONES, :CLIENTEPERFIL_ID
attr_accessible :DFECHAINGRESO, :BIDENTIFICADO, :BACEPTAENVIOSMS, :IFOTO, :COLATIPOPREFERIDA_ID
attr_accessible :BACTIVO, :CNROJUGADOR, :CSEXO, :DFECHANACIMIENTO, :NACIONALIDAD_ID, :CEMAIL2, :CPATROCINADOR, :CPAISRESIDENCIA, :CPROVINCIARESIDENCIA, :CCIUDADRESIDENCIA
attr_accessible :DFECHAHORA_CESIONIMAGEN, :BFIRMA, :BJUGADORFRECUENTE, :DFECHAACTUALIZACION
def as_json(options={})
nick = self.CNICKNAME.force_encoding("ISO-8859-1").encode("UTF-8")
name = self.CNOMBRES.force_encoding("ISO-8859-1").encode("UTF-8") unless self.CNOMBRES.nil?
surname = self.CAPELLIDO.force_encoding("ISO-8859-1").encode("UTF-8") unless self.CAPELLIDO.nil?
{:CLIENTE_ID => self.CLIENTE_ID ,:CNICKNAME => nick ,:CAPELLIDO => surname ,:CNOMBRES => name, :CDNI => self.CDNI, :TIPODOCUMENTO_ID => self.TIPODOCUMENTO_ID }
end
end
i always get errors like this:
incompatible character encodings: ASCII-8BIT and UTF-8
the only way i solved is doing something like this
modelinstance.field.force_encoding("ISO-8859-1").encode("UTF-8")
There are a lot of field on that table. Beside, sure i will need to query other tables like this one.
Looking in the database properties the collation is Modern_Spanish_CI_AS
How i can configure rails to show and save property in the collation Modern_Spanish_CI_AS
Thanks in advance, hope to be clear
Well I finally solve the problem.
I need to use tiny_tds:
here is the explanation:
https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/wiki/Using-TinyTds

Have created a database, and i am trying to connect it to my model and execute the SQL queries through the model only. What to do?

What the hell is wrong with this code? And how to run this code?
Please explain me little thoroughly as I am new to ruby. Please help!!!
my_connection_base.rb
class MyConnectionBase < ActiveRecord::Base
client = Mysql2::Client.new(:host => "localhost", :database => "flipkart_project_development", :table => "users" , :username => "flipkart_project", :password => "mypass")
results = client.query("SELECT * FROM users")
puts results
end
In Rails you do not need to write a raw SQL queries.
Just put all your database configurations in config/database.yml file like below.
development:
adapter: mysql2
database: flipkart_project_development
username: flipkart_project
password: yourpass
pool: 5
timeout: 5000
test:
adapter: mysql2
database: flipkart_project_test
username: flipkart_project
password: yourpass
pool: 5
timeout: 5000
production:
adapter: mysql2
database: flipkart_project_production
username: flipkart_project
password: yourpass
pool: 5
timeout: 5000
Rails will take care of establishing connection and executing queries.
In your case generate a model named user
rails g model user
In your project you can fetch all the users like below.
User.all
#This will generate the SQL query "select * from users"
#and will return the records as user objects.
Take a look at migrations too.
The code as given would be difficult to execute on demand: it would be executed when the model was loaded. It should be placed in a method:
class MyConnectionBase < ActiveRecord::Base
def some_method
client = Mysql2::Client.new(:host => "localhost", :database => "flipkart_project_development", :table => "users" , :username => "flipkart_project", :password => "mypass")
results = client.query("SELECT * FROM users")
puts results
end
end
This could then be called in your controllers with MyConnectionBase.some_method.
But why you would want to do this eludes me completely. It would be much better to do it all the Rails way (see my comment above).

How do i work with two different databases in rails with active records?

I need to use different database connections in different Rails models. Is there a not-so-hacky way to do that?
Any links or search keywords would be great :)
Add new sections to your database.yml e.g.
other_development:
adapter: mysql
database: otherdb_development
username: root
password:
host: localhost
other_production:
adapter: mysql
database: otherdb_production
username: root
password:
host: localhost
Add a class in lib/other_database.rb
class OtherDatabase < ActiveRecord::Base
establish_connection "other_#{RAILS_ENV}"
end
and then for each model which isn't in the default database subclass from OtherDatabase e.g.:
class MyModel < OtherDatabase
# my model code...
end
I have been using the following to connect to 2 db in the same app. I put them in lib folder since everything in there is loaded.
require 'active_record'
class OldDatabase < ActiveRecord::Base
self.abstract_class = true
establish_connection(
:adapter => 'mysql',
:database => 'weather',
:host => 'localhost',
:username => 'root',
:password => 'password'
)
end
class NewDatabase < ActiveRecord::Base
self.abstract_class = true
establish_connection(
:adapter => 'mysql',
:database => 'redmine',
:host => 'localhost',
:username => 'root',
:password => 'password'
)
end
class WeatherData < OldDatabase
end
class Board < NewDatabase
end
Hope that helps
mikej is right. I did however write a gem that makes the model code to connect a little bit cleaner, check it out.
Update for Rails 3.x:
class MyModel < ActiveRecord::Base
establish_connection "other_#{Rails.env}"
end
Rails 6 added native support for multiple databases: https://edgeguides.rubyonrails.org/active_record_multiple_databases.html
database.yml
development:
one:
<<: *default
other:
<<: *default
Model base classes:
class OneModelBase < ActiveRecord::Base
around_action :set_db
private
def set_db
ActiveRecord::Base.connected_to(database: :one) do
yield
end
end
end
class OtherModelBase < ActiveRecord::Base
around_action :set_db
private
def set_db
ActiveRecord::Base.connected_to(database: :other) do
yield
end
end
end
You'll also have different migrations and schemas per DB. Running a command like rails db:create will create all databases. You can scope commands, e.g. rails db:create:other.
I think that the prettiest way to connect to another database with active model is creating base class for external database, and then inherite from that base in your model.
This method works fine with rails 4.2.6 and 5.0.4
For example:
# in /models/external_db/base.rb
require 'active_record'
class ExternalDb::Base < ActiveRecord::Base
self.abstract_class = true
establish_connection "external_db_#{Rails.env}".to_sym
end
And in your model class:
# in /models/external_db/some_model.rb
class ExternalDB::SomeModel < ExternalDb::Base
# your code
end
But you must define external database in /config/database.yml
# in /config/database.yml
external_db_development:
adapter: sqlite3
pool: 5
timeout: 5000
database: db/external_db_development.db
external_db_production:
adapter: sqlite3
pool: 5
timeout: 5000
database: db/external_db_production.db
In rails 4.1+ establish_connection now takes a symbol:
class OtherDbModel < ActiveRecord::Base
establish_connection :"other_#{Rails.env}"
end

Resources