2012-09-20 17 views
30

Esto puede ser una pregunta simple, pero parece que estoy tirando de mi cabello para encontrar una solución elegante aquí. Tengo dos clases del modelo ActiveRecord, con una asociación has_one y belongs_to entre ellos:Encontrar nil tiene_una asociación en donde consulta

class Item < ActiveRecord::Base 
    has_one :purchase 
end 

class Purchase < ActiveRecord::Base 
    belongs_to :item 
end 

Busco una manera elegante de encontrar todos los objetos artículo, que no tienen objeto de compra asociado a ellos, a ser posible sin tener que recurrir a tener un booleano is_purchased o un atributo similar en el artículo.

Ahora mismo tengo:

purchases = Purchase.all 
Item.where('id not in (?)', purchases.map(&:item_id)) 

que funciona, pero parece ineficaz para mí, ya que está llevando a cabo dos consultas (y compras podría ser un conjunto de registros masiva).

carriles de rodadura 3.1.0

Respuesta

33

Es bastante tarea común, SQL combinación externa por lo general funciona bien para él. Eche un vistazo a here, por ejemplo.

En le caso de tratar de usar algo como

not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null") 
+1

¡Ese enlace es perfecto! La unión es más o menos exactamente lo que necesitaba, solo tuve que sustituir el lugar de arriba con 'where ('' purchase.item_id IS null '')' y está bien. ¡Gracias! –

+0

Si usa 'has_many' o' has_one' puede lograr la unión sin tener que escribir la consulta real usando 'Item.join (: compras)' –

21

encontrado dos otras maneras railsey de hacer esto:

Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL") 

Item.includes(:purchase).where(purchases: { id: nil }) 

Técnicamente el primer ejemplo de trabajo sin la cláusula 'referencias' pero Carriles 4 asadores advertencias de depreciación sin él.

+0

Si bien esto es más sucinto que la respuesta aceptada, cargará todos los datos de la tabla asociada. Si desea mantener su consulta SQL ordenada y ordenada, utilice la combinación personalizada que en su lugar sugirió @dimuch. – deadwards

+0

@deadwards No creo que sea cierto. 'Asset.includes (: attachments) .where (attachments: {id: nil})' produce: 'SELECT" assets "." Property_id "AS t0_r0, ..." attachments "." Updated_at "AS t1_r7 FROM" assets " IZQUIERDA EXTERIOR ÚNETE "attachments" ON "attachments". "Property_id" = "assets". "Property_id" WHERE "attachments". "Id" IS NULL', igual que el ejemplo de @dimuch. – bronson

+0

Rieles de 4 vías: 'Item.includes (: compras) .references (: compras) .where (compras: {id: nil})'. Si llamas a_sql en él, verás que definitivamente no carga todos los datos. Hace exactamente lo mismo que la respuesta aceptada. – bronson

3

Una versión más concisa de solución @dimuch es utilizar el método left_outer_joins introducido en los carriles 5:

Item.left_outer_joins(:purchase).where(purchases: {id: nil}) 

Nótese que en la llamada left_outer_joins:purchase es singular (que es el nombre del método creado por el has_one declaración), y en la where cláusula :purchases es plural (aquí es el nombre de la tabla a la que pertenece el campo id).

+1

¿Se puede especificar la versión de Rails? Creo que esto es> 5. – niborg

+0

Gracias @niborg. Es una buena idea. – ReggieB

Cuestiones relacionadas