If I understand right, this is your problem:
- A single Rails App handles both READ and WRITE requests from a Mobile App
- You want the single Rails App to use different READ Database and WRITE Database
- And you want to be able to custom control when the WRITEs get propagated to the READ DB
The cleanest way to solve your problem is:
Create two databases (let's call them READ DB and WRITE DB), and setup Master/Slave replication between them. So whatever query you make in the WRITE DB, it gets replicated in the READ DB, and you can control when and how that replication gets triggered. Nearly every database supports this out-of-the-box. Here are instructions for MySQL. You don't even have to switch to PostgreSQL for this, because Mater/Slave replication is standard and robust on either.
For the Rails app, you will now configure two databases in config/database/yml
:
production:
# configuration for your WRITE DB
adapter: mysql
database: ...
host: ...
username: ...
password: ...
production_read:
# configuration for your READ DB
adapter: mysql
database: ...
host: ...
username: ...
password: ...
And then you have two choices:
Deploy two instances of the same. There is no change in code, but just deploy two instances, one instance with RAILS_ENV=production_read
for READ and another with RAILS_ENV=production
for WRITE. Change your mobile app so that READs goes to the first instance URL and WRITEs go to the other instance URL.
(OR)
Have only one Rails app instance running, and just switch between the production
and production_read
databases. Since you're having a proper Web Service, I'm going to assume that you're using GET requests for reading data. All other (POST/PUT/DELETE/etc) requests are write requests. If you aren't doing this, then I first suggest that you do this anyways. Given that you're using GET for reads, then you would do the following:
# app/controllers/application_controller.rb
class ApplicationController
before_filter do
# Switch to production_read or production configurations
# based on request method
if request.get?
ActiveRecord::Base.establish_connection "production_read"
else
ActiveRecord::Base.establish_connection "production"
end
end
This will switch between your production_read
and production
configurations depending on the request method. You can also order the before_filter
so that the switching happens only after your authentications and authorizations, and finally it happens just before your controller logic.
It is also sometimes required to do the same establish_connection
for all model classes as well. So in that case, you would just loop through the ActiveRecord::Base
subclasses and call the same establish_connection logic. Heck, you could even omit some of the subclasses before switching the connections!
ActiveRecord::Base.descendants.each do |model_class|
model_class.establish_connection (request.get? ? "production_read" : "production")
end
# Or let's say you want to switch all models *except* User/Session models to the READ DB
(ActiveRecord::Base.descendants - [User, Session]).each do ...