Pregunta

Tengo una tabla de relación:

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

vinculación de los animales a los demás. La mejor manera es posible recuperar asociaciones en SQL es:

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)

Y no puedo encontrar una manera de crear una relación has_many adecuada en los carriles con esto. Al parecer, no hay manera de proporcionar las condiciones de unión para has_many.

Actualmente estoy usando un 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}'  

pero este método tiene la gran desventaja de romper la magia activerecord. Por ejemplo:

@animal.friends.first 

ejecutará la finder_sql sin límite, ir a buscar miles de filas, a continuación, teniendo el primero de la serie (y perder varios preciosos segundos / req).

Creo que es una característica que falta de la AR, pero me gustaría estar seguro primero :) Gracias

¿Fue útil?

Solución

Usted podría resolver esto en el nivel de base de datos con vistas, lo cual sería el método correcto de todos modos.

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
)

Si no quieres tener entradas dobles para las relaciones con los amigos en ambos sentidos que podría hacer esto:

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
)

Se necesitaría una forma de decidir qué status_id a utilizar en caso de que haya dos los conflictivos. Elegí MIN (status_id) pero que no es probable que lo que desea.

En Rails puede hacer esto ahora:

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

Esto está fuera de mi cabeza y no probado. Además, es posible que tenga algún plug-in para el manejo vista en rieles (como "redhillonrails-core").

Otros consejos

Hay un plugin que hace lo que quiere.

Hay un post sobre ello aquí .

Hay una alternativa aquí .

Ambos permiten que usted hace a las condiciones de unión y es justo con la inicialización perezosa. Así que usted puede utilizar condiciones dinámicas. Me parece que el ex más bonita, pero puede utilizar este último si no desea instalar plugins.

Los dos maneras de crear relaciones muchos a muchos en registro activo son has_and_belongs_to_many y has_many: a través. Este sitio critica las diferencias entre los dos . Usted no tiene que escribir ningún SQL utilizando estos métodos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top