0

私は関係テーブルを持っています:

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 関係を作成する方法が見つかりません。どうやら、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}'  

しかし、この方法にはアクティブレコードの魔法が壊れるという大きな欠点があります。例えば ​​:

@animal.friends.first 

finder_sql を無制限に実行し、数千行をフェッチしてから、配列の最初のものを取得します (そして、貴重な数/ req を失います)。

ARに欠けている機能だと思いますが、最初に確認したいです:)ありがとう

4

3 に答える 3

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
)

2 つの競合する status_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」など)。

于 2009-05-26T06:11:31.580 に答える
1

あなたが望むことをするプラグインがあります。

それについての投稿があります here .

ここに別の方法があります。

どちらも条件を結合することができ、遅延初期化を使用しているだけです。したがって、動的条件を使用できます。前者の方がきれいだと思いますが、プラグインをインストールしたくない場合は後者を使用できます。

于 2009-10-26T18:00:02.777 に答える
0

アクティブなレコードで多対多の関係を作成する 2 つの方法は、has_and_belongs_to_many と has_many :through です。 このサイトでは、この2 つの違いを批判しています。これらのメソッドを使用して SQL を記述する必要はありません。

于 2009-04-25T12:04:40.920 に答える