Не можете определить: объединяет условия в отношении has_many? - PullRequest
0 голосов
/ 25 апреля 2009

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

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, но я хотел бы сначала убедиться :) Спасибо

Ответы [ 3 ]

2 голосов
/ 26 мая 2009

Вы можете решить эту проблему на уровне базы данных с помощью представления, которое в любом случае будет правильным методом.

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

Это из моей головы и не проверено. Также вам может понадобиться некоторый плагин для обработки представлений в рельсах (например, "redhillonrails-core").

1 голос
/ 26 октября 2009

Есть плагин, который делает то, что вы хотите.

Есть пост об этом здесь .

Здесь есть альтернатива .

Оба позволяют вам присоединиться к условиям и просто используют ленивую инициализацию. Таким образом, вы можете использовать динамические условия. Я считаю, что первое красивее, но вы можете использовать второе, если не хотите устанавливать плагины.

0 голосов
/ 25 апреля 2009

Существует два способа создания отношений «многие ко многим» в активной записи: has_and_belongs_to_many и has_many: through. Этот сайт критикует различия между ними. Вам не нужно писать какие-либо SQL с использованием этих методов.

...