ActiveRecord: trucos con campos nil en las condiciones

Posted by joahking
on Aug 19, 08

ActiveRecord y olores a SQL

Digamos que tienes un objeto que referencia a otro, Hoja que tiene un arbol_id, si quieres contar las hojas caidas te puedes llevar una sorpresa:
>> Hoja.count(:conditions => ["arbol_id = ?", nil])
=> 0
Miremos en el log la query resultante:
SQL (0.000286) SELECT count(*) AS count_all FROM `hojas` WHERE (arbol_id = NULL) 
El problema es que la comparacion SQL para el valor NULL debiera ser IS y no =. Si pruebas de esta otra manera obtendras el resultado puntual deseado:
>> Hoja.count(:conditions => ["arbol_id IS ?", nil])
=> 272
# y la query correcta:
SQL (0.000286) SELECT count(*) AS count_all FROM `hojas` WHERE (arbol_id IS NULL) 

Pero el ActiveRecord adapter te lanzara una exception si en vez del nil pasas un numero como parametro. Este problema ocurre tambien usando :conditions con los find

Solucion

Si no aparece alguna mejor solucion, nos queda el workaround del find_all_by_xxx con length:
>> Hoja.find_all_by_arbol_id(nil).length
=> 272
# y la query queda bien:
Hoja Load (0.000554)   SELECT * FROM "hojas" WHERE ("hojas"."arbol_id" IS NULL)

# find_all_by... es mi heroe (menos performantico que un count, pero mi heroe):
>> Hoja.find_all_by_arbol_id(1).length
=> 300
# y otra query bien construida:
Hoja Load (0.000554)   SELECT * FROM "hojas" WHERE ("hojas"."arbol_id" = 1)¡

La raiz de los males, OOP y Datamapper

En el tratamiento de las :conditions del ActiveRecord se filtran hasta los objetos olores a SQL, lo cual no es muy Object Oriented. Miremoslo en Datamapper:

# wow! que sintaxis mas DRY (49 characteres AR vs 28 DM)
>> Hoja.count(:arbol_id => nil)
=> 272

# y estamos mas separados del SQL:
>> Hoja.count(:arbol_id => 1)
=> 300

# incluso podemos hacer comparaciones concisas (en este caso greater than):
>> Hoja.count(:arbol_id.gt => 1)
=> 435

Datamapper hace muy buen trabajo manteniendonos a salvo en tierra OOP.

Comments

Leave a response

  1. Carlos ParamioAug 19 08 @ 08:44PM
    Estoy de acuerdo con que DataMapper es genial. No obstante, siempre puedes usar un hash para las condiciones en ActiveRecord:

    >> Hoja.count(:conditions => {:arbol_id => nil})

    >> Hoja.count(:conditions => {:arbol_id => 1})

    Obtener todos los objetos para sólo contarlos con length (o size) es una auténtica locura. Tal vez te funcione bien con pocos objetos, pero para una cantidad seria, no sólo llevaría muchísimo tiempo, sino que se merendaría la memoria de tu máquina en un periquete.
  2. joahkingAug 19 08 @ 09:54PM
    @Carlos: si esta es la solucion. Muchas gracias.
Comment