ActiveRecord y olores a SQL
Digamos que tienes un objeto que referencia a otro, Hoja que tiene unarbol_id, si quieres contar las hojas caidas te puedes llevar una sorpresa:
>> Hoja.count(:conditions => ["arbol_id = ?", nil]) => 0Miremos 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 delfind_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.
>> 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.