Asociación de Rails "has_many" con clave externa NULL

Nuestra aplicación Rails 3 tiene modelos Person y Message. Los mensajes pueden ser específicos para una Persona (cuando el mensaje person_id se establece la columna) o pueden ser "globales" (cuando el person_id columna es NULL).

Nos gustaría tener un sencillo has_many relación usando el :conditions opción como tal:

class Person < ActiveRecord::Base
  has_many :messages,
      :conditions => proc { ['(messages.person_id IS NULL) OR ' +
                             '(messages.person_id = ?)'], self.id }
  # ...
end

Pero parece que el has_many El método de clase codifica la opción "condiciones" como una cláusula lógica "Y" después de aplicar la restricción de clave externa de igualdad a la identificación del objeto Persona (por ejemplo, "FROM messages WHERE person_id=123 AND (person_id IS NULL OR person_id=123)"). Parece que no hay forma de permitir que los objetos asociados con claves foráneas nulas pertenezcan a tales asociaciones.

¿Rails 3/ActiveRecord proporciona una forma de hacer esto o debo hackear mis propios métodos de asociación?

preguntado el 03 de mayo de 12 a las 15:05

2 Respuestas

No puede tener una cláusula OR como desea usar condiciones en la asociación ActiveRecord. Puede tener la asociación sin condiciones y luego agregar un método para incluir los mensajes globales que desee. De esa manera, aún podría aprovechar la asociación al crear registros asociados.

# person.rb
has_many :messages

def all_messages
  Message.where('messages.person_id=? OR messages.person_id IS NULL', id)
end

Aquí está mi enchufe estándar para el Chillido gem, que es útil para consultas más avanzadas como esta si no desea tener bits de SQL en su código. Echale un vistazo.

Respondido el 07 de Septiembre de 12 a las 13:09

desafortunadamente, esto significa que no puede hacer cosas como: Person.limit(2).includes(:all_messages), ya que all_messages no es una asociación. - joe van dyk

@JoeVanDyk Sí, tienes razón. Sin embargo, hacer include(:all_messages) ejecutaría una consulta SQL adicional para realizar la carga ansiosa. Entonces, más allá de la sintaxis, no hay una diferencia real en términos de rendimiento con respecto a mi sugerencia. - Mago de Ogz

Hacer el include(:all_messages) da como resultado un máximo de una consulta adicional. Si hizo Person.limit(100).includes(:all_messages), esto da como resultado dos consultas. Si usó #all_messages arriba, serían 101 consultas para cargar todo. - joe van dyk

Creo que tiene razón, podría renunciar a has_many y crear un alcance con una combinación externa, algo como:

class Person < ActiveRecord::Base
   scope :messages lambda { 
      joins("RIGHT OUTER Join messages on persons.id = messages.person_id")
      .where('persons.id = ?',self.id)
   }

contestado el 03 de mayo de 12 a las 17:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.