Cómo asignar una variable con el resultado de un if..bloque else?


Tuve una discusión con un colega sobre la mejor manera de asignar una variable en un if..else block. Su código original era :

@products = if params[:category]
  Category.find(params[:category]).products
else
  Product.all
end

Lo reescribí de esta manera:

if params[:category]
  @products = Category.find(params[:category]).products
else
  @products = Product.all
end

Esto también podría ser reescrito con un solo liner usando un operador ternery (? :) pero vamos a suponer que la asignación de productos era más larga que un carácter 100 y no podía caber en una línea.

¿Cuál de los dos es más claro para ti? La primera solución toma un poco menos de espacio, pero pensé que declarar un variable y asignarle tres líneas después puede ser más propenso a errores. También me gusta ver mi if y else alineado, hace que sea más fácil para mi cerebro para analizarlo!

Author: Pierre Olivier Martel, 2010-05-28

15 answers

Como alternativa a la sintaxis en la respuesta de badp , me gustaría proponer:

@products = 
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end

Reclamo que esto tiene dos ventajas:

  1. Sangría uniforme: cada nivel de anidación lógica está sangrada por exactamente dos espacios (OK, tal vez esto es solo una cuestión de gusto)
  2. Compacidad horizontal: Un nombre de variable más largo no empujará el código indentado más allá de la marca de columna 80 (o lo que sea)

Toma una línea extra de código, que yo normalmente no le gustaría, pero en este caso parece que vale la pena cambiar el minimalismo vertical por el minimalismo horizontal.

Descargo de responsabilidad: este es mi propio enfoque idiosincrático y no se hasta qué punto se usa en otros lugares de la comunidad Ruby.

Editar: Debo mencionar que la respuesta de matsadler también es similar a esta. Creo que tener alguna sangría es útil. Espero que eso sea suficiente para justificar hacer de esto un respuesta.

 39
Author: antinome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:44

Como programador de Ruby, encuentro el primero más claro. Deja claro que toda la expresión es una asignación con la cosa asignada determinada en base a alguna lógica, y reduce la duplicación. Se verá raro para las personas que no están acostumbradas a idiomas donde todo es una expresión, pero escribir su código para personas que no conocen el idioma no es un objetivo importante, a menos que sean específicamente sus usuarios objetivo. De lo contrario, se debería esperar que la gente tenga un pase familiaridad con él.

También estoy de acuerdo con la sugerencia de bp de que se podría hacer que se lea más claramente mediante la sangría de toda la expresión if para que todo esté visualmente a la derecha de la asignación. Es totalmente estético, pero creo que eso lo hace más fácil de raspar y debería ser más claro incluso para alguien que no esté familiarizado con el idioma.

Como un aparte: Este tipo de if no es en absoluto exclusivo de Ruby. Existe en todos los Lisp (Common Lisp, Scheme, Clojure, sucesivamente.), Scala, todos los MLs (F#, OCaml, SML), Haskell, Erlang e incluso el predecesor directo de Ruby, Smalltalk. Simplemente no es común en lenguajes basados en C (C++, Java, C#, Objective-C), que es lo que la mayoría de la gente usa.

 21
Author: Chuck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:15

No me gusta que uses espacios en blanco en tu primer bloque. Sí, soy pitonista, pero creo que hago un punto justo cuando digo que el primer podría parecer confuso en medio de otro código, tal vez alrededor de otros if bloques.

Qué tal...

@products = if params[:category] Category.find(params[:category]).products
            else                 Product.all
            end

@products = if params[:category]
              Category.find(params[:category]).products
            else                
              Product.all
            end

También podrías intentarlo...

@products = Product.all #unless a category is specified:
@products = Category.find(params[:category]).products if params[:category]

...pero eso es una mala idea si Product.all en realidad es una función similar que podría ser evaluada innecesariamente.

 16
Author: badp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 22:11:26

Encapsulación...

@products = get_products

def get_products
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end
 12
Author: Derick Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 21:37:12

Solo otro enfoque:

category = Category.find(params[:category]) if params[:category]
@products = category ? category.products : Product.all
 6
Author: Matías Flores,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 22:49:07

Otro enfoque sería usar un bloque para envolverlo.

@products = begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

Que resuelve el problema de la asignación. Sin embargo, son demasiadas líneas para un código tan "complejo". Este enfoque sería útil en caso de que quisiéramos inicializar la variable solo una vez:

@products ||= begin
  if params[:category]
    Category.find(params[:category]).products
  else
    Product.all
  end
end

Esto es algo que no se puede hacer con el código reescrito y está correctamente alineado.

 5
Author: Tombart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-15 16:44:35
@products =
if params[:category]
  Category.find(params[:category]).products
else
  Product.all
end

Es otra opción, evita repetir @products y mantiene el if alineado con else.

 2
Author: matsadler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 22:15:29

Asumiendo que tus modelos se ven así:

class Category < ActiveRecord::Base
  has_many :products
end
class Product < ActiveRecord::Base
  belongs_to :category
end

Podrías hacer algo aún más loco, como esto:

#assuming params[:category] is an id
@products = Product.all( params[:category] ? {:conditions => { :category_id => params[:category]}} : {})

O bien, puedes usar la funcionalidad sexy y perezosamente cargada named_scope:

class Product < ActiveRecord::Base
  ...

  #again assuming category_id exists
  named_scope :all_by_category, lambda do |cat_id|
    if cat_id
      {:conditions => {:category_id => cat_id}}
    end
  end

  #if params[:category] is a name, and there is a has and belongs to many
  named_scope :all_by_category, lambda do |cat_name|
    if cat_name
      {:joins => :categories, :conditions => ["categories.name = ?",cat_name]}
    end
  end
  ...
end

Usado como

@products = Product.all_by_category params[:category]
 2
Author: BaroqueBobcat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 22:17:08

Primero si se usa ternary, segundo si no.

El primero es casi imposible de leer.

 1
Author: Nate Noonen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 21:24:34

Tampoco soy una persona de Ruby, pero las campanas de alarma están sonando instantáneamente para el alcance del segundo comando, ¿esa variable estará disponible incluso después de que finalice su bloque if?

 1
Author: blissapp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 21:26:13

Diría que la segunda versión es más legible para las personas que no están familiarizadas con esa estructura en ruby. Así que + para ello! Por otro lado, la primera contrucción es más SECA.

A medida que lo miro un poco más, encuentro que la primera solución es más atractiva. Soy programador Ruby, pero no lo usé antes. ¡Seguro que empezaré!

 1
Author: klew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 21:34:05

Creo que el mejor código es :

@products = Category.find(params[:category])&.products.presence || Product.all

El "&" después de de find asegura que el método "products" no evaluará si la categoría es nil.

 1
Author: Julien 'JS',
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-04 10:18:40

Me parece que el segundo sería más legible para el programador típico. No soy un chico ruby, así que no me di cuenta de que un if/else devuelve un valor.... Entonces, para tomarme como ejemplo (y sí, esa es mi perspectiva :D), la segunda parece una buena opción.

 0
Author: joeslice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-27 21:23:55

Si uno está navegando a través del código, diría que el segundo bloque de código (el suyo), es definitivamente el que encuentro más fácil de comprender rápidamente.

El código de tu amigo está bien, pero la sangría, como señaló bp, hace una gran diferencia en este sentido.

 0
Author: Andres,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-28 14:36:34

No me gusta que uses paréntesis en tu primer bloque. Sí, soy un usuario de LISP, pero creo que hago un punto justo cuando digo que el primer podría parecer confuso en medio de otro código, tal vez alrededor de otros if bloques.

Qué tal...

@products = (if (params[:category])
        ((Category.find params[:category]).
            products)
    else
        (Product all)
    end
)

(^ Irónico, para exacerbar los problemas con la respuesta de @ badp)

 0
Author: Slipp D. Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:30