I have just started to use the octopus gem with rails, i tried like for the normal case
octopus:
replicated: true
fully_replicated: false
environments:
- development
development:
shard_one:
host: localhost
adapter: postgresql
database: app_development
the above i have used for the testing purpose also the main thing is used as the controller specific, not the model specific. I put this in application controller
def select_shard(&block)
Octopus.using(:shard_two, &block)
end
and call that method in appropriate controller, so its works, but when i move to heroku i have followed this https://devcenter.heroku.com/articles/distributing-reads-to-followers-with-octopus, because i need the reads only for some controller. so in this case how can i use that follower on controller specific?
Related
I have devise gem in my application. I've been playing around with making some tables etc. Now I want to separate the database for the admin and the database for the user. I know its one single database. But, I'm not sure how to get it going in Rails.
you can configure multiple databases in database.yml file
production:
primary:
database: my_primary_database
user: root
adapter: mysql
secondary:
database: my_secondary_database
user: secondary_root
adapter: mysql
migrations_paths: db/secondary_migrate
and then in your modal, you can mention which database to use
class AnimalsBase < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :secondary }
end
checkout this link for more detail https://edgeguides.rubyonrails.org/active_record_multiple_databases.html
**For rails 4.2 - 5 ** you can use this gem Multiverse
P.S: Rails 6 is coming with a more neat solution for this, a stable build for rails 6 is now available you can upgrade to newer version too.
Rails have some limitation in which connecting only single database is major one. So I used to switch database using configuration setup in database.yml where you will have development & admin_development, (test & production etc. also)
You can get following constants,
ADMINCONFIG = YAML.load_file("#{Rails.root}/config/database.yml")['admin_development']
DEVCONFIG = YAML.load_file("#{Rails.root}/config/database.yml")['development']
And later whenever you need, you can switch from one to another as per requirement (through controller),
ActiveRecord::Base.connection(ADMINCONFIG)
I created a multi tenancy app with Apartment gem and rails 5. I successfully create a new tenant, but I want to seed it. When I run the seeds file it states that the seed has run for this new tenant (Seeding tenant_name tenant), but there's no data there, only on public schema.
I can see 2 schemas created on PostgreSQL db,the public and the new one, but it only populates public schema. Why?
Tried putting on seeds.rb:
Apartment::Tenant.switch!('tenant_name')
And:
if Apartment::Tenant.current == "tenant_name"...
But no good.
Anyone?
Thanks in advance!
Your approach is correct, but please make sure these:
Ensure schema_search_path in PG:
Example: database.yml should look like:
default: &default
adapter: postgresql
schema_search_path: 'public,shared_extensions'
encoding: unicode
pool: 5
prepared_statements: false
development:
<<: *default
database: your_development_db
2. For schema-specific data population, run statement inside tenant switch block:
In seed.rb, first create tenant, then switch in that tenant like this:
Apartment::Tenant.switch('tenant_name') do
# Do all stuff here inside this block
# User.create(user_attributes) will create use only inside `tenant_name` schema
end
Cheer!
I'll try to keep this as brief as possible and open the question to discussion.
I have a Rails app which is a static codebase and runs on 9 different servers all the same db schema but of course with different values.
I wrote some SQL to query some dollar totals and will be putting this into either a rake task or a sidekiq worker and have it fire once a week to generate the data. Initially I was thinking of just throwing the resulting data from each server into a mailer and mailing it to whoever needs the data. This is pretty straight forward.
But there's a kink in this, we need to see metrics over time in highcharts or some other charting engine.
So here's my thought.
Create the sidekiq worker and fire it on a schedule
Take the resulting data from each server and populate it on a target server via Postgres (not sure how to do this)
The target server will have a very simple Rails app built that will have a model with metrics and an association for each server (ie server 1 server 2 etc), after populating the data via postgres (somehow) from the source servers, read the data in HighCharts and present the view
So that's my thought process so far. I'm not sure on how to get the data from the source servers via a live postgres call when the sidekiq worker fires. So that's problem #1. Problem #2 or more like question #2 is, would this be a better use case for creating some sort of consumable API on the target Rails server? If so, what's the best place to start.
If my question and thought process is unclear, please let me know so I can clarify and explain in better detail.
Cheers!
There are plenty of tutorials on how to use multiple database connections in Rails as well as building an API in Rails. A few minutes of Googling will give you plenty of examples. But here are a couple barebones approaches:
For multiple database connections, you are right, you'll need to have the connection info for both databases defined in your database.yml file. Example:
# Local Database
development:
adapter: mysql2
database: local_db
username: my_user
password: my_password
host: localhost
port: 3306
# Reporting Database
development_reporting_db:
adapter: postgresql
encoding: unicode
database: reporting
username: some_user
password: some_password
host: 1.2.3.4
port: 5432
Rails won't do anything with this extra block though unless you explicitly tell it to. The common practice is to define an abstract ActiveRecord model that will establish the second connection:
class ReportingRecord < ActiveRecord::Base
establish_connection( "#{Rails.env}_reporting_db".to_sym )
self.abstract_class = true
end
Then, create new models for tables that reside in your reporting database and inherit from ReportingRecord instead of ActiveRecord::Base:
class SomeModel < ReportingRecord
# this model sits on top of a table defined in database.yml --> development_reporting_db instead of database.yml --> development
end
For building an API, there are tons of different ways to do it. Regardless of your approach, I'd highly suggest you make sure it's only accessible via HTTPS. Here's a basic controller with one action that responds to json requests:
class ApiController < ApplicationController
before_filter :restrict_access # ensures the correct api token was passed (defined in config/secrets.yml)
skip_before_action :verify_authenticity_token # not needed since we're using token restriction
respond_to :json
def my_endpoint_action
render :json => {some_info: 'Hello World'}, :status => 200 # 200 = success
end
private
rescue_from StandardError do |e|
render :json => {:error => e.message}.to_json, :status => 400 # 400 = bad request
end
# ensures the correct api token was passed (defined in config/secrets.yml)
def restrict_access
authenticate_or_request_with_http_token do |token, options|
token == Rails.application.secrets[:my_access_token]
end
end
end
This example would require you to define an access token in your config/secrets.yml file:
development:
secret_key_base: # normal Rails secret key base
my_api_access_token: # put a token here (you can generate one on the command like using rake secret)
Choosing between an API and a multiple DB solution depends mostly on how your application might expand in the future. The multiple DB approach is typically easier to implement and has higher performance. An API tends to scale horizontally better and databases that have a connection from only one application instead of 2 or more tend to be easier to maintain over time.
Hope this helps!
I'm refactoring some features of a legacy php application which uses multiple databases with the same structure, one per language. When an user login choose his language and then all the following connections are made with the db for that app with some code like this:
$db_name = 'db_appname_' . $_SESSION['language'];
mysql_connect(...);
mysql_select_db($db_name);
I'd like to refactor also the database, but currently it's not an option because other pieces of software should remain in production with the old structure while the new app is developed, and for some time after it's been developed.
I saw this question, but both the question and the suggested gems are pretty old and it seems that they are not working with Rails 3.
Which is the best method to achieve this behavior in my new rails 3 app? Is there any other choice that avoid me to alter the db structure and fits my needs?
Last detail: in the php app even the login information are kept in separate tables, i.e. every db has its own users table and when the user logs in it also passes a language param in the login form. I'd like to use devise for auth in the new app which likely won't work with this approach, so I'm thinking to duplicate (I know, this is not DRY) login information in a separate User model with a language attribute and a separate db shared among languages to use devise features with my app. Will this cause any issue?
EDIT:
For completeness, I ended with this yml configuration file
production: &production
adapter: mysql
host: localhost
username: user
password: secret
timeout: 5000
production_italian:
<<: *production
database: db_app_ita
production_english:
<<: *production
database: db_app_eng
and with this config in base model (actually not exactly this but this is for keeping things clear)
MyModel < AR::Base
establish_connection "production_#{session[:language]}"
...
end
use establish_connection in your models:
MyModel < AR::Base
establish_connection "db_appname_#{session[:language]}"
...
end
Use MultiConfig gem I created to make this easy.
You could specify the configs in separate file like database_italian.yml etc and then call:
ActiveRecord::Base.config_file = 'database_italian'
This way it will be much easier to maintain and cleaner looking. Just add more language db config files as you wish
Can this be done? In a single application, that manages many projects with SQLite.
What I want is to have a different database for each project my app is managing.. so multiple copies of an identically structured database, but with different data in them. I'll be choosing which copy to use base on params on the URI.
This is done for 1. security.. I'm a newbe in this kind of programming and I don't want it to happen that for some reason while working on a Project another one gets corrupted.. 2. easy backup and archive of old projects
Rails by default is not designed for a multi-database architecture and, in most cases, it doesn't make sense at all.
But yes, you can use different databases and connections.
Here's some references:
ActiveRecord: Connection to multiple databases in different models
Multiple Database Connections in Ruby on Rails
Magic Multi-Connections
If you are able to control and configure each Rails instance, and you can afford wasting resources because of them being on standby, save yourself some trouble and just change the database.yml to modify the database connection used on every instance. If you are concerned about performance this approach won't cut it.
For models bound to a single unique table on only one database you can call establish_connection inside the model:
establish_connection "database_name_#{RAILS_ENV}"
As described here: http://apidock.com/rails/ActiveRecord/Base/establish_connection/class
You will have some models using tables from one database and other different models using tables from other databases.
If you have identical tables, common on different databases, and shared by a single model, ActiveRecord won't help you. Back in 2009 I required this on a project I was working on, using Rails 2.3.8. I had a database for each customer, and I named the databases with their IDs. So I created a method to change the connection inside ApplicationController:
def change_database database_id = params[:company_id]
return if database_id.blank?
configuration = ActiveRecord::Base.connection.instance_eval { #config }.clone
configuration[:database] = "database_name_#{database_id}_#{RAILS_ENV}"
MultipleDatabaseModel.establish_connection configuration
end
And added that method as a before_filter to all controllers:
before_filter :change_database
So for each action of each controller, when params[:company_id] is defined and set, it will change the database to the correct one.
To handle migrations I extended ActiveRecord::Migration, with a method that looks for all the customers and iterates a block with each ID:
class ActiveRecord::Migration
def self.using_databases *args
configuration = ActiveRecord::Base.connection.instance_eval { #config }
former_database = configuration[:database]
companies = args.blank? ? Company.all : Company.find(args)
companies.each do |company|
configuration[:database] = "database_name_#{company[:id]}_#{RAILS_ENV}"
ActiveRecord::Base.establish_connection configuration
yield self
end
configuration[:database] = former_database
ActiveRecord::Base.establish_connection configuration
end
end
Note that by doing this, it would be impossible for you to make queries within the same action from two different databases. You can call change_database again but it will get nasty when you try using methods that execute queries, from the objects no longer linked to the correct database. Also, it is obvious you won't be able to join tables that belong to different databases.
To handle this properly, ActiveRecord should be considerably extended. There should be a plugin by now to help you with this issue. A quick research gave me this one:
DB-Charmer: http://kovyrin.github.com/db-charmer/
I'm willing to try it. Let me know what works for you.
I got past this by adding this to the top of my models using the other database
class Customer < ActiveRecord::Base
ENV["RAILS_ENV"] == "development" ? host = 'devhost' : host = 'prodhost'
self.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "myuser",
:password => "mypass",
:database => "somedatabase"
)
You should also check out this project called DB Charmer:
http://kovyrin.net/2009/11/03/db-charmer-activerecord-connection-magic-plugin/
DbCharmer is a simple yet powerful plugin for ActiveRecord that does a few things:
Allows you to easily manage AR models’ connections (switch_connection_to method)
Allows you to switch AR models’ default connections to a separate servers/databases
Allows you to easily choose where your query should go (on_* methods family)
Allows you to automatically send read queries to your slaves while masters would handle all the updates.
Adds multiple databases migrations to ActiveRecord
It's worth noting, in all these solutions you need to remember to close custom database connections. You will run out of connections and see weird request timeout issues otherwise.
An easy solution is to clear_active_connections! in an after_filter in your controller.
after_filter :close_custom_db_connection
def close_custom_db_connection
MyModelWithACustomDBConnection.clear_active_connections!
end
in your config/database.yml do something like this
default: &default
adapter: postgresql
encoding: unicode
pool: 5
development:
<<: *default
database: mysite_development
test:
<<: *default
database: mysite_test
production:
<<: *default
host: 10.0.1.55
database: mysite_production
username: postgres_user
password: <%= ENV['DATABASE_PASSWORD'] %>
db2_development:
<<: *default
database: db2_development
db2_test:
<<: *default
database: db2_test
db2_production:
<<: *default
host: 10.0.1.55
database: db2_production
username: postgres_user
password: <%= ENV['DATABASE_PASSWORD'] %>
then in your models you can reference db2 with
class Customers < ActiveRecord::Base
establish_connection "db2_#{Rails.env}".to_sym
end
What you've described in the question is multitenancy (identically structured databases with different data in each). The Apartment gem is great for this.
For the general question of multiple databases in Rails: ActiveRecord supports multiple databases, but Rails doesn’t provide a way to manage them. I recently created the Multiverse gem to address this.
The best solution I have found so far is this:
There are 3 database architectures that we can approach.
Single Database for Single Tenant
Separate Schema for Each Tenant
Shared Schema for Tenants
Note: they have certain pros and cons depends on your use case.
I got this from this Blog! Stands very helpful for me.
You can use the gem Apartment for rails
Video reference you may follow at Gorails for apartment
As of Rails 6, multiple databases are supported: https://guides.rubyonrails.org/active_record_multiple_databases.html#generators-and-migrations
Sorry for the late and obvious answer, but figured it's viable since it's supported now.