Rails - 多索引键关联

4
有几种处理多个外键关联的方法。每种方法都有其缺点,由于我是Rails的新手,我相信其他人也遇到了类似的情况,并且可能已经解决了很久以前的问题。
我的问题是:
如何有效地处理多个索引键关联,同时仍保留所有其他Rails SQL修饰符(例如:include等)?
我的情景是:
我有一个表格关联,如下所示(简化),用于通过链接将人与其他人连接起来:
People
+----+-----------+
| id | name      |
+----+-----------+
| 1  | Joe       |
+----+-----------+
| 2  | Sally     |
+----+-----------+
| 3  | Bob       |
+----+-----------+                

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

从上面的链接表的第一行可以看出,一个人(Sally = 2)与另一个人(Joe = 1)相连。
如果我的外键是“origin_id”,那么我很容易找到一个人的所有链接。但这只会显示起始于一个人的链接。在我的场景中,我需要查看所有链接,无论它们是由一个人发起还是接收。例如,如果我要求查看Sally的所有链接(Sally = 2),我想要的结果将是:
Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1  | 2         | 1       |
+----+-----------+---------+                        
| 3  | 3         | 2       |
+----+-----------+---------+                        

因此,我有两个索引键,分别是“origin_id”和“rcvd_id”。
解决这个问题的一种方法是使用一个方法:
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

然而,这并不高效。例如,这需要从数据库中收集整个集合,然后才能使用 paginate 方法(我正在使用 will_paginate gem),这与其限制调用的记录数以加快进程的速度背道而驰。而是在整个集合完成之后再限制记录。
此外,上面的代码将不允许我调用例如 Joe.links(:first).origin_id.name 这样的代码。虽然不完全是这段代码的意思,但我的意思是,我无法调用所选链接的 origin_id 上的 Person 详细信息,因为 links 方法不知道 origin_id 与 People 表相关联。
到目前为止,最可行的解决方案似乎是使用 :finder_sql。
class Person < ActiveRecord::Base
  has_many :links, :finder_sql => 'SELECT * FROM links WHERE (links.origin_id = #{id} or links.rcvd_id = #{id})'

这将给出所有链接,其中Person_id与Links.origin_id或Links.rcvd_id匹配。
这种方法的缺点是,使用:finder_sql会排除所有其他sql修饰符,因为Rails不知道如何解析和修改提供的SQL。例如,我将无法在:finder_sql中使用:include选项。
所以,现在我正在使用:finder_sql解决方案。但似乎有一种方法可以完成此关联,而无需使用:finder_sql。例如,是否有一种方法编写自定义sql字符串,同时保留Active Record提供的Rails sql修饰符。
对以上内容有什么想法吗?
2个回答

3
我找到了解决方法,但实际上我可能问错了问题。我没有找到一种方法可以按我所要求的方式使用多个索引键,而不破坏不同的Rails帮助程序,除非实现一些自定义SQL。
我想我的问题仍然存在,但是我解决这个问题的方法是从不同的角度看待这个问题。我只是按照它们本来的关联方式创建了这些关联:
belongs_to :rcvd_person, :class_name => 'Person', :foreign_key => :rcvd_id
belongs_to :origin_person, :class_name => 'Person', :foreign_key => :origin_id

还有一个自定义的SQL语句:

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

然后我就可以在我的视图中按照自己的想法操作记录。如果有其他人也在做类似的事情,我是这样做的:

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

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

<% end %>

2

我不确定您是否可以在同一张表中支持使用多个键来关联,因为如果您尝试创建一个关系,Rails 将不知道该设置哪个键。

然而,如果您只想要 person.links,Rails 3 提供了一种比 :finder_sql 更好的方式。

class Link
  def self.by_person(person)
    where("origin_id => :person_id OR rcvd_id => :person_id", :person_id => person.id
  end 
end

class Person < ActiveRecord::Base
  # You can include the has_many relationships if you want, or not

  def links
    Link.by_person(self)
  end 
end

这使您能够执行类似于@person.links.limit(3)的操作(在使用:finder_sql时目前似乎存在问题)。

谢谢Lelon。我还没有跳到Rails3(我知道,很尴尬),但很快会研究一下这个。 - Oscar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接