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.
Related
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
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
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
I'm using active_record database without rails project like this.
require 'active_record'
ActiveRecord::Base.logger = Logger.new(STDERR)
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "posts.db")
unless File.exists? "posts.db"
ActiveRecord::Schema.define do
create_table :posts do |t|
t.column :title, :string
t.column :content, :text
end
end
end
class Post < ActiveRecord::Base
def do_something
# do_something
create(title: title, content: content)
end
end
Now I'm using Firefox add-on SQL Lite Manager to access the database.
But if possible I want to access database like rails console.
Is there a way to use rails console even if only active_record is used?
You can install pry gem.
And then in your current project directory add .pryrc.
In your .pryrc you can add something like this:
current_dir = Dir.pwd
$LOAD_PATH.unshift(current_dir)
Now, every time you go to pry session just require manually your file such as:
pry(main)> require 'models/post'
This the simplest method, you can customize or extend from here.
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