Variables locales opcionales en plantillas parciales de rails: ¿cómo salgo del desorden (definido? foo)?


225

Fui un niño malo y usé la siguiente sintaxis en mis plantillas parciales para establecer valores predeterminados para las variables locales si un valor no se definió explícitamente en: hash locals al representar el parcial -

<% foo = default_value unless (defined? foo) %>

Esto parecía funcionar bien hasta hace poco, cuando (sin ninguna razón que pudiera discernir) las variables no pasadas comenzaron a comportarse como si hubieran sido definidas como nulas (en lugar de indefinidas).

Como lo han señalado varias personas útiles en SO, http://api.rubyonrails.org/classes/ActionView/Base.html dice no usar

defined? foo

y en lugar de usar

local_assigns.has_key? :foo

Estoy tratando de enmendar mis formas, pero eso significa cambiar muchas plantillas.

¿Puedo / debo cobrar por adelantado y hacer este cambio en todas las plantillas? ¿Hay algún truco que deba vigilar? ¿Cuán diligentemente necesito probar cada uno?

Respuestas:


324

Hago esto:

<% some_local = default_value if local_assigns[:some_local].nil? %>

1
Aunque realmente me gusta la sintaxis compacta de la sugerencia de hgimenez (arriba), este enfoque tiene la ventaja de ser muy claro con respecto a lo que está sucediendo. (Todavía tiene el inconveniente de no permitirle pasar nada como valor para el local)
— brahn

1
¿Cuál es su caso de uso para querer pasar cero?
— jonnii

Oh, no tengo un caso específico en mente. Solo trato de entender todas las implicaciones. Esto me recordó aceptar esta respuesta :-)
— brahn

44
Para superar el problema nulo, estoy copiando el código del enlace del OP ( api.rubyonrails.org/classes/ActionView/Base.html ) <% if local_assigns.has_key? : headline%> Headline: <% = headline%> <% end%> - has_key evita la situación nula / falsa, y probablemente se puede acortar a una línea como la respuesta aquí
— Phil

55
Verifique la respuesta de Pablo: local_assigns.fetchmaneja perfectamente incluso las teclas con valor nulo. Devuelve un valor predeterminado solo si la clave no está configurada en absoluto.
— quetzalcoatl

158

Como local_assignses un hash, también puedes usar fetch con el opcional default_value.

local_assigns.fetch :foo, default_value

Esto volverá default_valuesi foono se configuró.

ADVERTENCIA:

Tenga cuidado con local_assigns.fetch :foo, default_valuecuándo default_valuees un método, ya que se llamará de todos modos para pasarle el resultado fetch.

Si su default_valuees un método, puede envolverlo en un bloque: local_assigns.fetch(:foo) { default_value }para evitar su llamada cuando no sea necesario.


1
Vale la pena decirlo explícitamente: los nilvalores se conservan aquí. Si el hash contiene :foomapeado nil, entonces fetchregresará nil. Es decir, al menos en mi v1.9.3. No recuerdo cómo se comportó 1.8.
— quetzalcoatl

Eso es totalmente correcto. Me recuerda el problema porque local_assigns[:foo] || default_value, cuando foo devuelve un valor falso, default_valuese utilizará en su lugar. Por lo general, es un problema para Memoization @some_value ||= expensive_methodsi el método devuelve un valor falso, siempre se ejecutará.
— Pablo Cantero

1
Cualquiera que no entienda por qué esta es la mejor respuesta no ha usado Ruby lo suficiente. Bravo Pablo!
— mastaBlasta

2
Una optimización es la siguiente, de modo que solo tiene que llamar a esto una vez en la parte superior de su plantilla, en lugar de usar el 'fetch guard' en cada uso de la variable. foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
— Sethcall

Me preguntaba por qué no funcionó, supuse que también creó la variable, pero todavía tenemos que usar el valor devuelto:foo = local_assigns.fetch :foo, true
— Vadorequest

84

Qué tal si

<% foo ||= default_value %>

Esto dice "usar foosi no es nulo o verdadero. De lo contrario, asignar default_valuea foo"


2
No estoy seguro de que esto funcione ya que foo no está definido a menos que se pase a través del hash local.
— jonnii

1
Esto funciona, pero si tiene valores predeterminados como este, ¿tal vez es una señal de que debería usar un ayudante?
— psyho

2
No hay magia aquí. Más recursos sobre el tema: groups.google.com/group/comp.lang.ruby/browse_thread/thread/…
— hgmnz

37
Realmente me gusta esta versión ya que la sintaxis es muy compacta. Sin embargo, supongo que la gran desventaja es que significa que no puede pasar nulo o falso como valor para el local, ya que se sobrescribirá por defecto.
— brahn

16
@brahn, ese es un buen punto. De hecho, esto debe evitarse si fooes un booleano. Con razón, puede tener el valor de false, y ser anulado default_valueaccidentalmente.
— hgmnz

10

Creo que esto debería repetirse aquí (de http://api.rubyonrails.org/classes/ActionView/Base.html ):

Si necesita averiguar si a una determinada variable local se le ha asignado un valor en una llamada de renderización particular, debe usar el siguiente patrón:

<% if local_assigns.has_key? :headline %>
  Headline: <%= headline %>
<% end %>

Prueba usando definido? El titular no funcionará. Esta es una restricción de implementación.


6

En mi caso, uso:

<% variable ||= "" %>

en mi parcial
No tengo idea si eso es bueno, pero para mí está bien


Esto realmente funciona bastante bien. Funciona para indefinido y al pasar nilcomo local en la llamada parcial también.
— Joshua Pinter

3
Ah, leyendo abajo, esto fallará si variablees un booleano y necesita configurarlo false. Utilizará el valor predeterminado en lugar de usar el falsevalor. ÚSELO BAJO SU PROPIO RIESGO.
— Joshua Pinter

5

Sé que es un hilo viejo pero aquí está mi pequeña contribución: lo usaría local_assigns[:foo].presenceen un condicional dentro del parcial. Luego configuro foosolo cuando sea necesario en la llamada de renderizado:

<%= render 'path/to/my_partial', always_present_local_var: "bar", foo: "baz" %>

Echa un vistazo a la guía oficial de Rails aquí . Válido desde RoR 3.1.0.


No puedo ver ninguna diferencia real entre local_assigns[:foo]y local_assigns[:foo].presence. Cualquiera de los dos volverá nilsi la clave no existe en el hash y el valor si existe.
— jamesmarkcook

1

Creo que una mejor opción que permite múltiples variables predeterminadas:

<% options = local_assigns.reverse_merge(:include_css => true, :include_js => true) %>
<%= include_stylesheets :national_header_css if options[:include_css] %>
<%= include_javascripts :national_header_js if options[:include_js] %>

1

Esta es una derivada de la respuesta de Pablo. Esto me permite establecer un valor predeterminado ('completo') y, al final, 'modo' se establece tanto en local_assigns como en una variable local real.

haml / slim:

- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')

erb:

<% mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full') %>

0

Más intuitivo y compacto:

<% some_local = default_value unless local_assigns[:some_local] %>


3
Creo que esto fallará si llamas al parcial con:locals => {:some_local => false}
— brahn

0

Si no desea pasar la variable local a parcial cada vez que la llame, haga esto:

<% local_param = defined?(local_param) ? local_param : nil %>

De esta forma evitas el undefined variableerror. Esto le permitirá llamar a su parcial con / sin variables locales.


O local_param = local_param si está definido? (Local_param)
— Kinaan Khan Sherwani

0

Ruby 2.5

Erb

Es posible, pero debe declarar sus valores predeterminados en el ámbito.

VARIABLE la palabra para reemplazo.

# index.html.erb
...
<%= render 'some_content', VARIABLE: false %>
...

# _some_content.html.erb
...
<% VARIABLE = true if local_assigns[:VARIABLE].nil? %>
<% if VARIABLE %>
    <h1>Do you see me?</h1>
<% end %>
...

-6

Se puede crear un asistente para que se vea así:

somearg = opt(:somearg) { :defaultvalue }

Implementado como:

module OptHelper
  def opt(name, &block)
    was_assigned, value = eval(
      "[ local_assigns.has_key?(:#{name}), local_assigns[:#{name}] ]", 
      block.binding)
    if was_assigned
      value
    else
      yield
    end
  end
end

Vea mi blog para obtener detalles sobre cómo y por qué.

Tenga en cuenta que esta solución le permite pasar nulo o falso como el valor sin que se anule.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.