variabili locali opzionali nei template parziali delle rotaie: come posso uscire dal pasticcio (definito? pippo)?


225

Sono stato un bambino cattivo e ho usato la seguente sintassi nei miei modelli parziali per impostare i valori predefiniti per le variabili locali se un valore non è stato definito esplicitamente nell'hash: locals durante il rendering del parziale -

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

Questo sembrava funzionare bene fino a poco tempo fa, quando (per nessuna ragione potevo discernere) le variabili non passate hanno iniziato a comportarsi come se fossero state definite nul (piuttosto che indefinite).

Come è stato sottolineato da varie persone utili su SO, http://api.rubyonrails.org/classes/ActionView/Base.html dice di non usare

defined? foo

e invece da usare

local_assigns.has_key? :foo

Sto cercando di modificare i miei modi, ma questo significa cambiare molti modelli.

Posso / dovrei semplicemente caricare in anticipo e apportare questa modifica in tutti i modelli? C'è qualche inganno che devo cercare? Con quale diligenza devo provare ciascuno?

Risposte:


324

Lo faccio:

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

1
Anche se mi piace molto la sintassi compatta del suggerimento di hgimenez (sopra), questo approccio ha il vantaggio di essere molto chiaro riguardo a cosa sta succedendo. (Ha ancora il rovescio della medaglia di non farti passare zero come valore per il locale)
brahn

1
Qual è il tuo caso d'uso per voler passare zero?
jonnii,

Oh, non ho in mente un caso specifico. Sto solo cercando di capire le implicazioni complete. Questo mi ha ricordato di accettare questa risposta :-)
brahn

4
Per superare il problema zero, sto copiando il codice dal link dell'OP ( api.rubyonrails.org/classes/ActionView/Base.html ) <% if local_assigns.has_key? : titolo%> Titolo: <% = titolo%> <% end%> - has_key evita la situazione zero / falso e probabilmente può essere abbreviato in una riga come la risposta qui
Phil

5
Si prega di controllare la risposta di Pablo: local_assigns.fetchgestisce perfettamente anche i tasti con valore zero. Restituisce un valore predefinito solo se la chiave non è impostata affatto.
quetzalcoatl,

158

Dato che local_assignsè un hash, puoi anche usare fetch con il facoltativo default_value.

local_assigns.fetch :foo, default_value

Questo tornerà default_valuese foonon è stato impostato.

AVVERTIMENTO:

Fai attenzione a local_assigns.fetch :foo, default_valuequando default_valueè un metodo, in quanto verrà chiamato comunque per passarne il risultato fetch.

Se il tuo default_valueè un metodo, puoi avvolgerlo in un blocco: local_assigns.fetch(:foo) { default_value }per impedire la sua chiamata quando non è necessario.


1
Vale la pena dirlo esplicitamente: i nilvalori sono conservati qui. Se l'hash contiene :foomappato su nil, allora fetchtornerà nil. Cioè, almeno sulla mia v1.9.3. Non ricordo come si è comportato 1.8.
quetzalcoatl,

È assolutamente giusto. Mi ricorda il problema perché local_assigns[:foo] || default_value, quando foo restituisce un valore errato, default_valueverrà utilizzato invece. Di solito è un problema per Memoization @some_value ||= expensive_methodse il metodo restituisce un valore errato, verrà sempre eseguito.
Pablo Cantero,

1
Chi non capisce perché questa è la risposta migliore non ha usato il rubino abbastanza a lungo. Bravo Pablo!
mastaBlasta,

2
Un'ottimizzazione è la seguente, quindi devi chiamarla solo una volta in cima al tuo modello, invece di usare la 'fetch guard' su ogni uso della variabile. foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
sethcall

Mi chiedevo perché non funzionasse, supponevo che creasse anche la variabile, ma dobbiamo comunque usare il valore restituito:foo = local_assigns.fetch :foo, true
Vadorequest

84

Che ne dite di

<% foo ||= default_value %>

Questo dice "usa foose non è nullo o vero. Altrimenti assegna default_valuea pippo"


2
Non sono sicuro che funzioni perché Foo non è definito a meno che non sia passato attraverso l'hash della gente del posto.
jonnii,

1
Funziona, ma se hai valori predefiniti come questo, forse è un segno che dovresti usare un aiuto?
psyho,

2
Nessuna magia qui. Altre risorse sull'argomento: groups.google.com/group/comp.lang.ruby/browse_thread/thread/…
hgmnz

37
Mi piace molto questa versione poiché la sintassi è così compatta. Suppongo che il grande svantaggio sia che significa che non è possibile passare zero o falso come valore per il locale, poiché verrà sovrascritto dal valore predefinito.
brahn,

16
@brahn, è un buon punto. In effetti, questo dovrebbe essere evitato se fooè un valore booleano. Può legittimamente avere il valore di false, ed essere sovrascritto default_valueaccidentalmente.
hgmnz,

10

Penso che questo dovrebbe essere ripetuto qui (da http://api.rubyonrails.org/classes/ActionView/Base.html ):

Se è necessario scoprire se a una determinata variabile locale è stato assegnato un valore in una determinata chiamata di rendering, è necessario utilizzare il modello seguente:

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

Test usando definito? il titolo non funzionerà. Questa è una limitazione di implementazione.


6

Nel mio caso, utilizzo:

<% variable ||= "" %>

nel mio parziale.
Non ho idea se va bene, ma per me va bene


In realtà funziona abbastanza bene. Funziona per undefined e quando passa anche nilcome locale nella chiamata parziale.
Joshua Pinter,

3
Ah, leggendo qui sotto, questo fallirà se variableè un valore booleano e devi impostarlo su false. Utilizzerà il valore predefinito invece di utilizzare il falsevalore. UTILIZZARE A PROPRIO RISCHIO.
Joshua Pinter,

5

So che è un vecchio thread, ma ecco il mio piccolo contributo: lo userei local_assigns[:foo].presencein modo condizionale all'interno del parziale. Quindi ho impostato foosolo quando necessario nella chiamata di rendering:

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

Dai un'occhiata alla guida ufficiale di Rails qui . Valido da RoR 3.1.0.


Non riesco a vedere alcuna differenza reale tra local_assigns[:foo]e local_assigns[:foo].presence. Uno restituirà nilse la chiave non esiste nell'hash e il valore se esiste.
jamesmarkcook,

1

Penso che un'opzione migliore che consenta più variabili predefinite:

<% 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

Questo è un derivato della risposta di Pablo. Questo mi permette di impostare un valore predefinito ('full') e, alla fine, 'mode' è impostato sia in local_assigns che in una variabile locale effettiva.

Haml / sottile:

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

Erb:

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

0

Più intuitivo e compatto:

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


3
Penso che fallirà se chiami il parziale con:locals => {:some_local => false}
brahn

0

Se non si desidera passare la variabile locale al parziale ogni volta che la si chiama, procedere come segue:

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

In questo modo si evita l' undefined variableerrore. Questo ti permetterà di chiamare il tuo parziale con / senza variabili locali.


O local_param = local_param se definito? (Local_param)
Kinaan Khan Sherwani

0

Ruby 2.5

Erb

È possibile, ma è necessario dichiarare i valori predefiniti nell'ambito.

VARIABILE la parola per la sostituzione.

# 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

Un aiutante può essere creato per assomigliare a questo:

somearg = opt(:somearg) { :defaultvalue }

Implementato come:

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

Vedi il mio blog per dettagli su come e perché.

Si noti che questa soluzione consente di passare zero o false come valore senza che venga sovrascritto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.