Aug 19 ActiveRecord: trucos con campos nil en las condiciones

tags: activerecord conditions datamapper | comments

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.

blog comments powered by Disqus