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.