views:

36

answers:

1

There seem to be a number of ways to handle a multiple foreign key association. Each way I have approached this has their draw backs, and as I am new to Rails I am convinced others have come across a similar scenario and I am probably working on something solved long ago.

My question is:

What would be an efficient way of handling a multiple index key association, while still retaining all other Rails sql modifiers (such as :include etc)?

My scenario is:

I have a table association as follows (simplified), which is used to connect people to other people via links:

People
+----+-----------+
| id | name      |
+----+-----------+
| 1  | Joe       |
+----+-----------+
| 2  | Sally     |
+----+-----------+
| 3  | Bob       |
+----+-----------+                

Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1  | 2         | 1       |
+----+-----------+---------+
| 2  | 1         | 3       |
+----+-----------+---------+                        
| 3  | 3         | 2       |
+----+-----------+---------+                        

From row 1 of the above Links table, one can see that a Person (Sally = 2) is linked to another Person (Joe = 1).

It is easy for me to find all of a Persons Links if my foreign key was "origin_id". But this would only show People originating a Link. In my scenario I need to see all links regardless if they were originated or received by a Person. If for example I were to ask for all of Sally's links (Sally = 2), the result I would want would be:

Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1  | 2         | 1       |
+----+-----------+---------+                        
| 3  | 3         | 2       |
+----+-----------+---------+                        

Hence I have 2 index keys, both "origin_id" and "rcvd_id".

One way this could be solved is with a Method:

class Person < ActiveRecord::Base
  has_many  :link_origins,  :class_name => "Link", :foreign_key => :origin_id, :dependent => :destroy
  has_many  :link_rcvds,    :class_name => "Link", :foreign_key => :rcvd_id, :dependent => :destroy

def links
  origin_person + rcvd_person
end

However, this is not efficient. For example this requires the entire collection to be gathered from the database and only then does the paginate method work (I am using the will_paginate gem), which defeats the point as paginate should speed up the process by limiting the number of records called. Not limit the records after the entire collection is already done.

Also, the above will not allow me to call for example, Joe.links(:first).origin_id.name. Not exactly this code but meaning I could not call the Person details on the origin_id of a selected link, as the links method does not know that origin_id is related to the People table.

So far the most workable solution seems to be the :finder_sql.

class Person < ActiveRecord::Base
  has_many :links, :finder_sql => 'SELECT * FROM links WHERE (links.origin_id = #{id} or links.rcvd_id = #{id})'

This gives all links where the Person_id matches either the Links.origin_id or the Links.rcvd_id.

The down side of this option, is that using :finder_sql, rules out all the other sql modifiers since Rails doesn't know how to parse and modify the SQL you provide. For example I would not be able to use the :include option with the :finder_sql.

So, right now I am using the :finder_sql, solution. But it seems there might be a away of getting this association done in such a way that I don't need a :finder_sql. For example, is there a way to write a custom sql string while retaining the Rails sql modifiers that Active Record supplies.

Any ideas on the above?

A: 

I did find the solution to this, however it turned out I was probably asking the wrong question. I have not found away to have multiple index keys as I asked without implementing some custom sql which breaks different rails helpers.

I guess my question still stands, but how I did resolve this was to look at the problem differently. I just created the associations as they are:

belongs_to :rcvd_person, :class_name => 'Person', :foreign_key => :rcvd_id
belongs_to :origin_person, :class_name => 'Person', :foreign_key => :origin_id

And a custom sql statement:

class Person...
  has_many :links, :finder_sql => 'SELECT * FROM links WHERE origin_id = #{id} OR rcvd_id = #{id}'
end

Then I was able to manipulate the records how I wanted in my view. In case anyone else is doing something similar, I did:

<% person.links.each do |link| %> 

  <% if link.origin_id == person.id %>
    <%= link.rcvd_person.given_name %>
  <% else %>
    <%= link.origin_person.given_name %>
  <% end  %>

<% end %>
Dale