4

We currently have a rails app (basically a CRUD) that is used as a web service for a mobile application.

Right now, when users modify the content in the app, the changes are live in the mobile application because they are using the same database.

Is there any way we can put a separation between the live data (fetched by the app) and the data users can modify in the CRUD ? Is there a DBMS feature we can use for that, or a gem ?

We are currently using MySQL, but we are actively looking for alternatives (Postgresql, for instance).

Edit: We decided to use a filesystem cache in the meantime, to serve a non up to date version of the content for everyone. We then proceed to an invalidation when everything is all right so everyone can have the latest version. But I don't think this is a reliable solution...

Edit2: The whole purpose of this question is that we want to have some control over what gets read by the mobile application, some kind of moderation. We want to be able to manage changes and different states/versions/snapshots basically.

Pierre-Olivier
  • 3,104
  • 19
  • 37

4 Answers4

3

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:

  1. 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)

  2. 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 ...
Subhas
  • 14,290
  • 1
  • 29
  • 37
  • Awesome solution ! My only concern though is, would it be possible to control what changes get replicated or not to the read database ? To have some kind of "moderation", so that not everything can get to the "production* environment ? Would it be possible to do the synchronization (replication) manually ? – Pierre-Olivier Jun 17 '13 at 13:04
  • Replication works at Table level, but not at Row level. So you can't filter specific rows during replication. So if I'm right - do you want the ability to accept/revert changes, and then sync? – Subhas Jun 17 '13 at 13:13
  • Yes, that was the purpose of all this question, I'm sorry if my question was not enough clear (english is not my first language !). But I did not know it was possible to replicate at the table level, I though it was only at the database level. – Pierre-Olivier Jun 17 '13 at 13:19
0

What you are looking for, if i get your question right, is to separate production db from user editable content and run some validation / invalidation method upon syncing.

It sounds like a messaging system would do the trick for you.

Something like:

+-----------+            +------------+
|           |            |            |
|           |            |            |
|  ProdDb   |            |  UserDB    |<--------- Content
|           |            |            |
|           |            |            |
+-----------+            +------------+
     ^                          +
     |                          |
     |                          |
     |                          |
     |        +-----------+     |
     |        | RabbitMQ  |     |
     +-------+|           |<----+
              |           |
              |           |
              |           |
              |-----------|
              |    Q1     |
              +-----------+
  1. Users write data to the so called UserDB
  2. Then the data is posted to the message queue Q1 in the messaging system (i recommend RabbitMQ
  3. A worker then reads the next queue message with the user data and validates it, if it is ok - it is moved the prodDB for writing.
Elad Meidar
  • 774
  • 6
  • 11
0

The vestal_versions gem or the paper_trail gem could help you. There are great Railscasts episodes about them:

messivanio
  • 2,263
  • 18
  • 24
0

Looking at the high level problem description this makes me think that perhaps a database view is what you need. Or perhaps the more agile solution would be a lower privileged user.

Then you could have the non CRUD users fetching the data through a separate Rails model which connects to the other lower privileged database connection. Rails 3 can do this easily. The bulk of the problem would be in your database. Essentially setting up the database with Select only privileges.

Bottom line is you should be able to solve this on the database level and accomodate it on the Rails level.

Community
  • 1
  • 1
engineerDave
  • 3,887
  • 26
  • 28