Establish a connection to another database only in a block? - ruby-on-rails

In a rails application, I have this code in pure ruby :
class LinkCreator
attr_accessor :animal
def initialize(animal:)
#animal = animal
end
def call
"something#{link_id}"
end
private
def link_id
connection.execute(sql_request).first.first
end
def sql_request
"SELECT field FROM table WHERE field_id = '#{field_id}' LIMIT 1"
end
def field_id
animal.field_id
end
def connection
ActiveRecord::Base.establish_connection(
adapter: "mysql",
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USERNAME"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB_NAME"]
).connection
end
end
As you can see, this is not a model but only a simple class. The problem is than the connection of activerecord is changed and the other requests, later, are executed on the new connection.
Is it possible to establish a connection only in a block and go back to the old connection. I know I can establish another connection but this is very bad for performance.

It would be nice if you keep all database connections in database.yml
development:
adapter: mysql2
other stuff...
db_2:
adapter: mysql2
other stuff..
other_envs:
.....
Then create a class
class OtherDB < ActiveRecord::Base
establish_connection(:db_2)
end
From your controller you can access just like
OtherDB.table_name = "table_name"
OtherDB.first
Check my blog here http://imnithin.github.io/multiple-database.html

Have found most short example in Rails codebase at activerecord/test/support/connection_helper.rb and slightly adapted:
def with_another_db(another_db_config)
original_connection = ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(another_db_config)
yield
ensure
ActiveRecord::Base.establish_connection(original_connection)
end
Usage (given that you have another_db: section in your database.yml):
with_another_db(ActiveRecord::Base.configurations['another_db']) do
ActiveRecord::Base.connection.execute("SELECT 'Look ma, I have changed DB!';")
end

You can perform some queries within a block. First, define some module which will extend ActiveRecord, as below. This is a part of code used in production to change db connection per each request as well as to temporarily switch db to perform some queries within another database.
# RAILS_ROOT/lib/connection_switch.rb
module ConnectionSwitch
def with_db(connection_spec_name)
current_conf = ActiveRecord::Base.connection_config
begin
ActiveRecord::Base.establish_connection(db_configurations[connection_spec_name]).tap do
Rails.logger.debug "\e[1;35m [ActiveRecord::Base switched database] \e[0m #{ActiveRecord::Base.connection.current_database}"
end if database_changed?(connection_spec_name)
yield
ensure
ActiveRecord::Base.establish_connection(current_conf).tap do
Rails.logger.debug "\e[1;35m [ActiveRecord::Base switched database] \e[0m #{ActiveRecord::Base.connection.current_database}"
end if database_changed?(connection_spec_name, current_conf)
end
end
private
def database_changed?(connection_spec_name, current_conf = nil)
current_conf = ActiveRecord::Base.connection_config unless current_conf
current_conf[:database] != db_configurations[connection_spec_name].try(:[], :database)
end
def db_configurations
#db_config ||= begin
file_name = "#{Rails.root}/config/database.yml"
if File.exists?(file_name) || File.symlink?(file_name)
config ||= HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(file_name)).result))
else
config ||= HashWithIndifferentAccess.new
end
config
end
end
end
ActiveRecord.send :extend, ConnectionSwitch
Now you can use it as below:
ActiveRecord.with_db("db_connection_name") do
# some queries to another db
end

If you want to connect to postgres sql, you can use pg ruby gem & add the below code inside the block.
postgres = PG.connect :host => <host_name>, :port => <port>, :dbname => <database_name>, :user => <user>, :password => <password>
tables = postgres.exec(query)
tables.num_tuples.times do |i|
print tables[i]
end
To connect to mysql db inside a block, use mysql2 ruby gem & add the below code inside the block.
db = Mysql2::Client.new ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(<database_url>).to_hash
data = db.query(<<-SQL)
select * from students
SQL
print data

I use environment variables taken from Heroku's DATABASE_URL to connect to different databases:
class Database
def self.development!
ActiveRecord::Base.establish_connection(:development)
end
def self.production!
ActiveRecord::Base.establish_connection(ENV['PRODUCTION_DATABASE'])
end
def self.staging!
ActiveRecord::Base.establish_connection(ENV['STAGING_DATABASE'])
end
end
e.g.:
Database.production!; puts User.all.map(&:name)
Database.staging!; puts User.all.map(&:name)

It might help to use an instance variable to store the connection. Something like this:
def connection
#connection ||= ActiveRecord::Base.establish_connection(
adapter: "mysql",
host: ENV["MYSQL_HOST"],
username: ENV["MYSQL_USERNAME"],
password: ENV["MYSQL_PASSWORD"],
database: ENV["MYSQL_DB_NAME"]
).connection
end
That way the existing connection is retrieved on future connection attempts, rather than establishing a new one.

Try active_record_slave gem:
Ruby gem: active_record_slave

Related

How to use ActiveRecord in module?

require 'active_record'
module Module1
module Module2
class Database
ActiveRecord::Base.establish_connection(
:adapter => 'postgresql',
:host => 'db_url',
:username => 'db_username',
:password => 'db_password',
:database => 'db_name'
)
class People < ActiveRecord::Base
end
end
end
end
I can move the ActiveRecord establish connection bit into a an initialize function, and instantiate the class however, I still get a NoMethod error when I try to call the People class.
The goal is to use it outside of the module/class by doing something like the following:
db = Module1::Module2::Database
db.People.all.each do |Person|
puts Person
end
The answer is to move into another module so it doesn't require instantiation, and it's not in a class:
require 'active_record'
module Module1
module Module2
module Database
ActiveRecord::Base.logger = Logger.new(STDOUT)
class People < ActiveRecord::Base
end
end
end
end
Then to use it:
# get a configuration object like this similiar to how rails is configured
dbconfig = YAML::load(File.open('config/database.yaml'))
db.establish_connection(dbconfig["database1"])
db = Module1::Module2::Database::People
db.all.each do |x|
puts x.created_at
end
# switch between profiles
db.establish_connection(dbconfig["database2"])
db = Module1::Module2::Database::People
db.all.each do |x|
puts x.created_at
end
config/database.yaml should resemble this:
database1:
adapter: <adapter_name>
username: <username>
password: <password>
host: <host>
database: <database_name>
This approach is "single threaded" in the sense that you can only be connected to one database at a time because ActiveRecord connections overwrite each other as you switch between them. So switch get your data and switch again.

How can I establish a connection to multiple databases, with the same adapter(postgres), using active_record gem?

how are you? I've been having some troubles when I try to establish a connection to two databases in postres. I will try to describe the scenario:
This is my pg.yml:
default: &default
adapter: postgresql
encoding: unicode
pool: 5
host: localhost
user: xxxxx
password: xxxxx
development:
<<: *default
database: not_legacy
legacy:
<<: *default
database: legacy
This is my Legacy::Base class:
#/models/legacy/base.rb
require 'active_record'
require 'erb'
module Legacy
class Base < ActiveRecord::Base
self.abstract_class = true
conf_contents = File.read('config/pg.yml')
conf_evaluated = ::ERB.new(conf_contents).result
conf = YAML.load(conf_evaluated)
ActiveRecord::Base.establish_connection conf['legacy']
end
end
and this is my NotLegacy::Base class:
#/models/not_legacy/base.rb
require 'active_record'
require 'erb'
module NotLegacy
class Base < ActiveRecord::Base
self.abstract_class = true
conf_contents = File.read('config/pg.yml')
conf_evaluated = ::ERB.new(conf_contents).result
conf = YAML.load(conf_evaluated)
ActiveRecord::Base.establish_connection conf['not_legacy']
end
end
Also, I have two classes that inherit from the classes previously described.
This is Legacy::Company:
#/models/legacy/company.rb
require_relative 'base'
module Legacy
class Company < Base
self.table_name = "company"
end
end
and NotLegacy::Company:
#/models/not_legacy/company.rb
require_relative 'base'
module NotLegacy
class Company < Base
self.table_name = "company"
end
end
Now, if I go to the console and do something like(I'm printing conf value):
irb(main):001:0> load 'app/models/legacy/company.rb'
CONFS: {"adapter"=>"postgresql", "encoding"=>"unicode", "pool"=>5, "host"=>"localhost", "user"=>"xxxxx", "password"=>"xxxxx", "database"=>"legacy"}
=> true
irb(main):002:0> Legacy::Company.count
=> 8
irb(main):003:0> load 'app/models/not_legacy/company.rb'
CONFS: {"adapter"=>"postgresql", "encoding"=>"unicode", "pool"=>5, "host"=>"localhost", "user"=>"xxxxx", "password"=>"xxxxx", "database"=>"not_legacy"}
=> true
irb(main):004:0> NotLegacy::Company.count
=> 1
At this point everything seems to work correctly since in the legacy database there are 8 records for company and in the not_legacy database there is only 1 record. But if I call Legacy::Company again, I get:
irb(main):005:0> Legacy::Company.count
=> 1
irb(main):005:0> NotLegacy::Company.count
=> 1
It seems that the second connection (made to the not_legacy database) is overwriting the first one (the one made to the legacy database).
If anyone of you can explain me why this is happening and how to fix it I will be immensely grateful
Thanks.
First I would like to go with basic about how you are going through code flow,
When you load any class, its code is executed & methods defined, your connection is created through static code written & establish_connection is executed.
Single instant of rails app can have connection to single database
You better to write code to switch through different database to have access for different database connectivity.
Legacy::Company or NotLegacy::Company both models have same table name companies from different databases. But when you connect particular database both model (which got loaded) will point to companies table in current database.
So what is current database mean to both model ?
-> The one which is loaded last time by establish_connection.
So this is little tedious but it is working that if you deal with different databases, you have to keep switching from one database to another one.
on config/database.yml
other_base:
adapter: postgresql
encoding: unicode
pool: 5
host: localhost
user: xxxxx
password: xxxxx
on your model
class YourModel < ActiveRecord::Base
self.establish_connection :other_base
end

ActiveRecord: how to share connections between AR-models when using ActiveRecord without Rails

am trying to use ActiveRecord without Rails, to create a gem that connects to a MySql database. The connection to the database should be settable, because the gem is intended mainly for console use, so I don't want to provide the database connection information beforehand in a YAML file, but provide it "on-the-fly". This seems to bring a lot of problems, since the ActiveRecord models are loading the database information on initialisation. Is there any other way of sharing the database information or some way to make active_record not preload the database configurations? Is there maybe a better way to share connection information than "establish_connection"?
here is my code:
class Blog
##options = { :adapter => "mysql2" }
def self.options
##options
end
def initialize(options = Hash.new)
##options = {
:adapter => "mysql2",
:host => options[:host] || "localhost",
:username => options[:user] || "root",
:password => options[:pass] || "",
:database => options[:db] || "my_blog"
}
end
end
module Foobar
class Post < ActiveRecord::Base
establish_connection(Blog.options)
end
end
on the command line
Blog.new(user:"foo",pass:"bar",db:"bang")
p=Foobar::Post.all
You should just call ActiveRecord::Base.establish_connection(...).
class Blog
# No need to use initializer method if you don't intend
# to use initialized instance. Static method will do better.
def self.connect(options = Hash.new)
ActiveRecord::Base.establish_connection(
adapter: "mysql2",
host: options[:host] || "localhost",
username: options[:user] || "root",
password: options[:pass] || "",
database: options[:db] || "my_blog"
)
end
end
module Foobar
class Post < ActiveRecord::Base
# Connection is done via Blog model
end
end
Blog.connect(username: 'john', password: 'qwerty')
posts = Foobar::Post.all

Copy between databases in Rails console

I have three databases:
A. mynewapp_psql (Postgres)
B. old_products_psql (Postgres)
C. old_blogposts_mysql (Mysql)
Each is defined in database.yml
I'm using A (mynewapp_psql) as the database for my new app. In this app I want to be able to copy selected material from my two older databases.
My attempt (updated according to response)
old_db = ActiveRecord::Base.establish_connection(:database => 'old_blogposts_mysql'... etc)
posts = old_db.connection.execute("select * from posts'")
posts.each do |p|
NewPost.create(:name => p.name.downcase) #NewPost should add Post in A. (mynewapp_psql)
end
It should take each product from my old database and create a new equivalent in the new database.
I really prefer doing it through the console and I can't copy the database straight over since I need to filter and alter the data.
So your case is one of Lost connection. How about Establishing a new connection to your current DB so that your code becomes like this
old_db = ActiveRecord::Base.establish_connection(:database => 'old_blogposts_mysql'... etc)
posts = old_db.connection.execute("select * from posts'")
new_db = ActiveRecord::Base.establish_connection(:database => 'mynewapp_psql'... etc)
posts.each do |p|
NewPost.create(:name => p.name.downcase) #NewPost should add Post in A. (mynewapp_psql)
end
The console is not the only way to dynamically access your databases. Think about a custom ruby task for your purposes using ActiveRecord connections for each database.
require 'pg'
require 'active_record'
ActiveRecord::Base.establish_connection(
adapter: 'postgresql', # or 'mysql2' or 'sqlite3'
host: 'localhost',
database: 'canvas_test',
username: 'joe',
password: 'shiitake'
)
# EXEC raw SQL
ActiveRecord::Base.connection.execute("SELECT * FROM posts;")
class Post < ActiveRecord::Base
end
# READ all posts
puts Post.count
=> #<Post:0xb96582cc>
=> ...
# CREATE one new post
my_post = Post.new

Can't Rails 3 run in MySQL MyISAM mode, without InnoDB?

I have a MySQL server running with InnoDB disabled (for reasons of performance), with this setup I can't seem to be able to use Rails 3 (with the mysql2 adapter).
Here's my test migration:
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts, :options => 'ENGINE=MyISAM') do |t|
t.timestamps
end
end
end
And here's the error:
>rake db:migrate
rake aborted!
Mysql2::Error: Unknown storage engine 'InnoDB': CREATE TABLE `schema_migrations`
(`version` varchar(255) NOT NULL) ENGINE=InnoDB
Tried the workaround described here, but it doesn't seem to work either (I did modify MysqlAdapter to Mysql2Adapter to match my setup).
Sorry I'm a newb in Rails. Any help will be much appreciated :o
Going to answer my own question. Here's a patch for environment.rb I ended up with that works with the native mysql driver as well as JRuby/JDBC-mysql:
# Load the rails application
require File.expand_path('../application', __FILE__)
# Patch Mysql adapter to default to MyISAM instead of InnoDB
require 'active_record/connection_adapters/mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, options.reverse_merge(:options => "ENGINE=MyISAM"))
end
end
end
end
# Initialize the rails application
.....
rake db:migrate now succeeds and creates all tables including schema_migrations with TYPE=MyISAM.
Note: For mysql2 adapter, rename mysql_adapter to mysql2_adapter and MysqlAdapter to Mysql2Adapter.
Try creating the table without specifying the type of engine being used like this
class CreateTxts < ActiveRecord::Migration
def change
create_table(:txts) do |t|
t.timestamps
end
end
end
and then in mysql cli, type this
ALTER TABLE txts ENGINE = MYISAM
hope it helps
The error is coming from creating the schema_migrations table (which rails uses to track which migrations have been run) rather than your table. You could create that table yourself (with a single varchar(255) column called version with an index on it).
If you do end up overwriting the create_table method, you need to preserve the method's signature - you're ignoring the block that it yields. I'd try something like
def create_table(name, options={})
super(name, options.merge(...)) {|t| yield t}
end
#rustyx nice one! &here's my slightly tweaked monkey-patch so the engine can be set from within the /config/database.yml configuration:
&Rather than in /config/environment.rb, i put the codes into an initializer, Eg: /config/initializers/mysql2adapter_default_engine.rb.
Either this for brevity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
class ActiveRecord::ConnectionAdapters::Mysql2Adapter < ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
or this for more clarity:
require 'active_record/connection_adapters/abstract_mysql_adapter'
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
def create_table(table_name, options = {}) #:nodoc:
super(table_name, #config[:engine].nil? ? options : options.reverse_merge(:options => "ENGINE="+#config[:engine]))
end
end
end
end
Then in /config/database.yml we can have something like this:
production:
adapter: mysql2
encoding: utf8
database: ooook
username: ooook
password: ooook
host: ooook.tld
port: 3306
pool: 5
timeout: 5000
engine: MyISAM
If no engine is specified then the mysql2 adapter will use the default (InnoDB or whatever).

Resources