Aclaremos los conceptos entre dos mundos: blank? y present? es Rails / empty? y nil? es Ruby


rubyonrails Hay momentos en los que llevamos mucho tiempo trabajando en proyectos de Ruby on Rails y perdemos la noción de la frontera ente dónde se encuentra cada cosa que utilizamos. A mí me ha pasado alguna vez, en ese caso es mejor parar y pensar por  un momento. También en alguna que otra ocasión he oído que no se entendía y/o sabía la diferencia o que siempre utilizo este método que me funciona siempre. Por esto me gustaría escribir este post para aclararlo.

Por tanto, si alguna vez has trabajado en proyectos Rails, seguramente estos métodos que voy a contar, te suenen e incluso los has utilizado en alguna ocasión, pero no te has parado a pensar el motivo de ¿porqué debo utilizarlos, qué es mejor, cuándo, dónde, alguna ventaja especial? así como algunas curiosidades que voy a explicar.

Métodos de Rails

En ocasiones hemos utilizado estos métodos sin saber dónde están la frontera de Rails y de Ruby. Tanto blank? como present? son métodos de Rails y los podemos utilizar en entornos de proyectos de Rails y dentro de una consola de Rails. Por ejemplo, cuando recuperamos un objeto de Active Record y queremos saber si tiene datos o no dependiendo de nuestros intereses de estética u otro tema, podemos utilizar cualquiera de estos métodos:

n = Notice.first

  Notice Load (10.6ms)  SELECT  “notices”.* FROM “notices”  ORDER BY “notices”.”id” ASC LIMIT 1

=> #<Notice id: 1, title: “Primera noticia”, created_at: “2015-11-29 19:45:08”, updated_at: “2015-11-29 19:45:34”>

2.2.1 :062 > n.present?

=> true

2.2.1 :063 > n.blank?

=> false

Pero veamos cada uno de ellos por separado.

BLANK?

Es un método de Rails – blank? y opera casi con cualquier objeto. La definición del método es:

def blank?
  respond_to?(:empty?) ? !!empty? : !self
end

Por ejemplo podemos comprobar que el resultado es true en los casos:

nil.blank? = true
[].blank? = true
{}.blank? = true
" ".blank? = true
"".blank? = true

Un recurso interesante: Cuando tengamos dudas sobre dónde se encuentra, podemos ejecutar el método source_location:


"".method(:blank?).source_location

=> ["/Users/carlossanchezperez/.rvm/gems/
ruby-2.2.1@accepts-nested-attributes-for/gems/
activesupport-4.2.0/lib/active_support/core_ext/
object/blank.rb", 116]
PRESENT?

Para el método present?  es lo contrario de blank? lo podemos comprobar en la definición de su método

# File activesupport/lib/active_support/core_ext/
  object/blank.rb, line 22

def present?
  !blank?
end

Métodos de Ruby

Ahora veamos los métodos que podemos utilizar en Rails igualmente pero cuando abrimos una consola de irb de Ruby son métodos que sólo podemos utilizarlos en ese entorno. Veamos cada método de Ruby, empty? y nil?

EMPTY?

Este método sólo está accesible para algunos objetos como los objetos de tipo Hash, Array y String. Si en otros casos, por ejemplo, con un objeto Active Record intentamos preguntar por si el objeto recuperado es empty? nos dirá que :

NoMethodError: undefined method `empty?’ for #<Notice:0x007fa6747b6a70>

En un momento veremos un caso en el que podremos utilizar empty? en una consulta de Active Record.

NIL?

En Ruby, todas las clases heredan de la clase Object class. nil? que es un método de objeto; Por lo tanto, a menos que explícitamente sea ignorado, todas las clases tienen acceso a nil?

Por ejemplo si accedemos a nuestro objeto de Active Record y le preguntamos:

n = Notice.first

  Notice Load (10.6ms)  SELECT  “notices”.* FROM “notices”  ORDER BY “notices”.”id” ASC LIMIT 1

=> #<Notice id: 1, title: “Primera noticia”, created_at: “2015-11-29 19:45:08”, updated_at: “2015-11-29 19:45:34”>

Podemos decir si el objeto resultante n es nil?. En este caso al obtener un objeto AR nos dirá FALSE, en caso contrario obtenemos TRUE, o si preguntamos si nil es nil? nos devuelve TRUE.

nil.nil?

=> true

Un detalle interesante FINAL de conocer

Cuando estamos trabajando para consultar algo en nuestra Base de Datos con Active Record y necesitamos consultar un array con los datos:

n = Notice.where(title: [“Primera noticia”, “Segunda noticia”])

  Notice Load (0.8ms)  SELECT “notices”.* FROM “notices” WHERE “notices”.”title” IN (‘Primera noticia’, ‘Segunda noticia’)

=> #<ActiveRecord::Relation [#<Notice id: 1, title: “Primera noticia”, created_at: “2015-11-29 19:45:08”, updated_at: “2015-11-29 19:45:34”>, #<Notice id: 2, title: “Segunda noticia”, created_at: “2016-03-30 20:43:01”, updated_at: “2016-03-30 20:43:01”>]>

Entonces hay diferencia en utilizar un método u otro:

2) n = Notice.where(title: [“Primera noticia”, “Segunda noticia”]).empty?

   (0.1ms)  SELECT COUNT(*) FROM “notices” WHERE “notices”.”title” IN (‘Primera noticia’, ‘Segunda noticia’)

=> false

2) n = Notice.where(title: [“Primera noticia”, “Segunda noticia”]).blank?

  Notice Load (0.2ms)  SELECT “notices”.* FROM “notices” WHERE “notices”.”title” IN (‘Primera noticia’, ‘Segunda noticia’)

=> false

Esto quiere decir que es mejor utilizar el método empty? pero también obtenemos el mismo resultado con any? para el caso de nuestro ejemplo, sí es cierto pero queda claro que con empty? necesitamos únicamente consultar una única vez a la Base de Datos con un count, un único paso, en vez de consultar primero a la Base de Datos, como en el caso de blank? y verificar el array resultante los resultados obtenidos, es decir, necesitamos hacerlo en varios pasos con blank? y en un paso con empty?.

Finamente si hacemos un benchmark sobre un ejemplo de utilización de empty?, present? y any? para un array de 500 mil elementos y de los que vamos a comprobar 100 mil veces en cada uno de los métodos del array, el proceso es:

Benchmark.bmbm do |x|

          x.report(“any with data:”)   { 100000.times { array.any? } }

          x.report(“present with data:”) { 100000.times { array.present? } }

          x.report(“empty with data:”) { 100000.times { array.empty? } }

end

Resultados obtenidos:

                          user     system      total        real

any with data:       0.000000   0.000000   0.000000 (  0.007800)

present with data:   0.010000   0.000000   0.010000 (  0.012611)

empty with data:     0.000000   0.000000   0.000000 (  0.008894)

En este caso any?, siendo un método de Ruby de Enumerable, se obtienen mejores resultados que utilizando empty? que se queda en segundo lugar, frente al peor resultado que se lo lleva present?.

Por tanto, para finalizar, es mejor probar y comprobar que nos va mejor en cada caso conociendo lo que hacemos y cómo lo hacemos en cada caso concreto.

2 comentarios en “Aclaremos los conceptos entre dos mundos: blank? y present? es Rails / empty? y nil? es Ruby

  1. Jose Galisteo Ruiz dijo:

    Para present? y blank? además hay que tener en cuenta el tamaño de la colección, porque si Notice.where(title: [“Primera noticia”, “Segunda noticia”]) resulta que tiene miles de elementos, Rails los cargará y “mapeará” como un array de objetos Notice.

    Así que creo que normalmente si lo que quieres es ver si hay elementos en una ActiveRelation (creo que se llama así) es usar esos métodos que hacen “count” cómo “empty?” o “exists?”

    Notice.sports.exists? por ejemplo.

    • carlossanchezperez dijo:

      Hola Jose, creo que estás afirmando lo que he explicado o así lo he entendido en tu comentario. El objetivo del post en principio, era saber en qué mundo no estamos moviendo y no confundirse, pero está bien la puntualización. Quisiera añadir que cuando estamos en el mundo de ActiveRecord::Relation tenemos que también algunas diferencias entre lo métodos de #count, #length, y #size. Por ejemplo, “count” siempre va a realizar una consulta para contabilizar los resultados, “length” cargará los registros primero y después llama a length sobre la matriz resultante. Pero “size” en su caso hará lo más inteligente en función de si lo tenemos cargado o no. Normalmente utilizo size, a menos que esté llamando inmediatamente antes de tener cargados los registros reales, en cuyo caso se utilizo length. Intento no utilizar count, ya que size omitirá la consulta si puede.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s