Aug 19 ActiveRecord: trucos con campos nil en las condiciones
tags:
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.