我有一张关系表:
create_table "animal_friends", :force => true do |t|
t.integer "animal_id"
t.integer "animal_friend_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "status_id", :default => 1
end
将动物与其他动物联系起来。在SQL中检索关联的最佳方法是:
SELECT animals.*
from animals join animal_friends as af
on animals.id =
case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end
WHERE #{id} in (af.animal_id, af.animal_friend_id)
我无法找到一种方法来在rails中创建一个正确的has_many关系。显然,没有办法为has_many提供加入条件。
我目前正在使用finder_sql:
has_many :friends, :class_name => "Animal", :finder_sql => 'SELECT animals.* from animals join animal_friends as af on animals.id = case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end ' +
'WHERE #{id} in (af.animal_id, af.animal_friend_id) and status_id = #{Status::CONFIRMED.id}'
但是这种方法有很大的缺点,就是打破了activerecord魔法。例如:
@animal.friends.first
将无限制地执行finder_sql,获取数千行,然后获取数组中的第一行(并丢失几个宝贵的秒 / req)。
我猜这是AR缺少的功能,但我想先确定:) 感谢
答案 0 :(得分:2)
您可以使用视图在数据库级别上解决此问题,无论如何这都是正确的方法。
CREATE VIEW with_inverse_animal_friends (
SELECT id,
animal_id,
animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
UNION
SELECT id,
animal_friend_id AS animal_id,
animal_id AS animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
)
如果您不想为有关系的朋友提供双重条目,您可以这样做:
CREATE VIEW unique_animal_friends (
SELECT MIN(id), animal_id, animal_friend_id, MIN(created_at), MAX(updated_at), MIN(status_id)
FROM
(SELECT id,
animal_id,
animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends
UNION
SELECT id,
animal_friend_id AS animal_id,
animal_id AS animal_friend_id,
created_at,
updated_at,
status_id
FROM animal_friends) AS all_animal_friends
GROUP BY animal_id, animal_friend_id
)
您需要一种方法来决定使用哪个status_id,以防有两个相互冲突的状态。我选择了MIN(status_id),但这可能不是你想要的。
在Rails中,您现在可以执行此操作:
class Animal < ActiveRecord::Base
has_many :unique_animal_friends
has_many :friends, :through => :unique_animal_friends
end
class UniqueAnimalFriend < ActiveRecord::Base
belongs_to :animal
belongs_to :friend, :class_name => "Animal"
end
这是我的想法,没有经过测试。此外,您可能需要一些插件来处理rails中的视图(例如“redhillonrails-core”)。
答案 1 :(得分:1)
有一个插件可以做你想要的。
有关于它的帖子here。
还有另一种here。
两者都允许你加入条件并且只是使用延迟初始化。 所以你可以使用动态条件。我发现前者更漂亮,但如果你不想安装插件,你可以使用后者。
答案 2 :(得分:0)
在活动记录中创建多对多关系的两种方法是has_and_belongs_to_many和has_many:through。 This site批评了两者之间的差异。您不必使用这些方法编写任何SQL。