Ruby on Rails Many To Many associations with multiple databases

Sometimes you need to use multiple databases in your Rails projects. Usually when some data must be shared between different applications. When this happens you usually have some models in a shared database, and some other models in the specific application database. This can be easily done using the establish_connection method in the shared models to tell them they have to connect to a different database.

However, when you need some interaction between those shared models and the models of your specific application, like a has_many, :through association, some problems arise. The typical Many To Many association uses an intermediate database table that links the relation between two models, and allows you to add some extra information on that relation. When navigating through the association, Rails tries to make an SQL query that joins the model with this intermediate table. For example, imagine you have a Team model, which has many Players, but a player can also be on more than one team. We use an intermediate model TeamPlayers (and we can also use it to save the role of that player into that team, for example). You would have those three tables:

  • teams
  • players
  • teams_players

When asking for the players of a given Team, Rails would do something similar to this:

SELECT "players".* FROM "players" INNER JOIN teams_players" ON "players".id = "teams_players".player_id WHERE "players".team_id = 1

Where 1 is the id of the team you asked for. This [obviously] works perfectly fine when everything is in the same database, and it’s as efficient as the SQL database manager you’re using. What happens, however, when we have the Player model in another database? It will miserably fail because Rails will try to join with a table that doesn’t exist.

Unfortunately, there’s no efficient way to solve this problem, that is, using SQL, as you can’t work with tables from different databases. However, there’s a rather elegant solution that Brian Doll cared to implement as a gem a while ago. As indicated in the GitHub readme, you just have to use a has_many_elsewhere relation instead of the usual one, and make sure that the model referenced has the connection established to the shared database. And that’s all.

The magic donde behind the scenes is pretty simple: this gem just replicates the same methods that the ActiveRecord::Base class does in the has_many method call, changing the failing unique SQL calls to double SQL calls, one for each database, fetching the intermediate models first, and then fetching the remote models using those ids.

This method is not perfect, as probably not all the goodness of the original association can be done with it, but for simple scenarios is more than enough.

Share

Create your own JSONP Proxy using Ruby on Rails

Today I was working on a web site that needs to retrieve some RSS feed over the internet. Since the web page has no server (HTML + javascript only) I couldn’t access the feed from the server side. Also, because of the Cross Domain limitation of Ajax requests, I couldn’t access the RSS in the client either. I searched Google for an API and found the Google Feed API, which does exactly what I want. However, because (I think) Google caches the RSS feed you request, there was a significant delay (about half an hour) between the update of the RSS contents and the RSS provided by Google (the feed was updated in a per minute basis, as it was a CoverItLive event). Seeing I couldn’t access really recent posts from the feed using Google, I decided to implement my own RSS API using JSONP in a ruby on rails environment, since having an external server act as a proxy was allowed for the overall solution.

The tools I needed I got from those two websites: http://rubyrss.com/ for a RSS parser, and http://blogs.sitepoint.com/2006/10/05/json-p-output-with-rails/ on how to build a simple JSONP response on the server side.

Basically you have to start creating a new controller that will handle the JSONP requests. In my case I just added a ‘Feed’ controller:

$ script/generate controller Feed

Then you edit the app/controllers/feed_controller.rb file and start coding. We will assume that the request will come in this form: http://server/feed/get?callback=some_callback&url=the_url_of_the_feed. Having this information, the controller code is pretty straightforward.

class FeedController < ApplicationController
 
  require 'rss/1.0'
  require 'rss/2.0'
 
  def get
    begin
      url_contents = Net::HTTP.get(URI.parse(params[:url]))
      rss = RSS::Parser.parse(url_contents, false)
      json = { "error" => false, "feed" => rss }.to_json
    rescue
      json = { "error" => true }.to_json
    end
    respond_to do |format|
      format.js { render_json json }
    end
  end
 
  def render_json(json)
    callback = params[:callback]
    response = begin
      if callback
        "#{callback}(#{json});"
      else
        json
      end
    end
    render({:content_type => :js, :text => response})
  end
end

The first two lines are the requirements for the RSS module, which will allow us to parse a RSS feed. After that, we start with the get request. In there, we use the Net::HTTP.get() method, which will retrieve a URL content using a GET request and return its contents. To do so, we need to pass it an uri parameter, which we can get from the entire URL using the method URI.parse(). After this call, we have the XML of the RSS feed in url_contents. What we have to do now is build an RSS object with this XML. We’ll do that by calling RSS::Parser.parse(). If you wish to do some modifications to the RSS contents, now is your change. In this simple example we’ll simply bulk it all to the response.

To build the response, we need a JSON object. If everything went as expected, we can create a JSON object by simply creating a ruby associative array and calling the to_json method on it:

json = { "error" => false, "feed" => rss }.to_json

If, on the contrary, we got an error (bad URL, bad RSS, whatever), we simply return the same JSON object with the error property set to true (that’s done in the rescue clause).

After we have this JSON object built, we simply have to output the results. To do so, we use the help of a method called render_json which we have added to the controller code. In this method we simply output the JSON if we provide no callback (this means no JSONP), or either a padded JSON (hence the name JSONP) with the callback name followed by the JSON data. In either case we render the results as a js type.

For more detailed information on how JSONP works, check http://en.wikipedia.org/wiki/JSON#JSONP, but what you basically need to know is that when you do a JSONP request what you’re really doing is retrieve a chunk of javascript code that will be run on your client, so be aware of the security issues you can have here.

Share