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?

Brahn
fuente

Respuestas:

324

Hago esto:

<% some_local = default_value if local_assigns[:some_local].nil? %>
jonnii
fuente
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.

Pablo Cantero
fuente
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"

hgmnz
fuente
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.

gamov
fuente
6

En mi caso, uso:

<% variable ||= "" %>

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

Moisés Portillo
fuente
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.

microspino
fuente
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] %>
Daniel OCallaghan
fuente
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') %>
sethcall
fuente
0

Más intuitivo y compacto:

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

muirbot
fuente
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.

Haris Krajina
fuente
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 %>
...
dimpiax
fuente
-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.

Jaime Cham
fuente