Не можете определить :объединяет условия в отношении has_many?

StackOverflow https://stackoverflow.com/questions/788582

Вопрос

У меня есть таблица отношений :

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, используя эти методы.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top