Не можете определить :объединяет условия в отношении has_many?
-
16-09-2019 - |
Вопрос
У меня есть таблица отношений :
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)
И я не могу найти способ создать правильное отношение has_many в rails с помощью этого.По-видимому, нет способа предоставить условия присоединения для 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, но я хотел бы сначала убедиться :) Спасибо
Решение
Вы могли бы решить это на уровне базы данных с помощью представления, которое в любом случае было бы правильным методом.
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").
Другие советы
Существует плагин, который делает то, что вы хотите.
Об этом есть пост здесь.
Есть альтернатива здесь.
Оба позволяют вам выполнять условия присоединения и просто используют отложенную инициализацию.Таким образом, вы можете использовать динамические условия.Я нахожу первое более привлекательным, но вы можете использовать второе, если не хотите устанавливать плагины.
Существует два способа создать отношения "многие ко многим" в активной записи: has_and_belongs_to_many и has_many :through. Этот сайт критикует различия между ними.Вам не нужно писать никакого SQL, используя эти методы.