opcjonalne zmienne lokalne w szablonach częściowych szyn: jak wyjść z (zdefiniowanego? foo) bałaganu?


225

Byłem złym dzieckiem i użyłem następującej składni w moich szablonach częściowych, aby ustawić wartości domyślne zmiennych lokalnych, jeśli wartość nie została wyraźnie zdefiniowana w haszu: locals podczas renderowania częściowego -

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

Wydawało się, że działa to dobrze do niedawna, kiedy (bez żadnego powodu mogłem rozpoznać) nieprzekazane zmienne zaczęły zachowywać się tak, jakby zostały zdefiniowane jako zero (a nie niezdefiniowane).

Jak zauważyły ​​różne pomocne osoby na SO, http://api.rubyonrails.org/classes/ActionView/Base.html mówi, aby nie używać

defined? foo

i zamiast tego użyć

local_assigns.has_key? :foo

Próbuję zmienić swoje sposoby, ale to oznacza zmianę wielu szablonów.

Czy mogę / powinienem po prostu pobierać opłaty z wyprzedzeniem i wprowadzać zmiany we wszystkich szablonach? Czy jest jakaś podstępność, na którą muszę uważać? Jak pilnie muszę testować każdy z nich?

Odpowiedzi:


324

Robię to:

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

1
Chociaż bardzo podoba mi się zwarta składnia sugestii hgimeneza (powyżej), to podejście ma tę zaletę, że bardzo jasno określa: co się dzieje. (Nadal ma tę wadę, że nie pozwala ci uchodzić za zero jako wartość lokalna)
brahn

1
Jaki jest twój przypadek użycia, jeśli chcesz przejść zero?
jonnii

Och, nie mam na myśli konkretnego przypadku. Próbuję zrozumieć pełne implikacje. To przypomniało mi o zaakceptowaniu tej odpowiedzi :-)
brahn

4
Aby rozwiązać problem zerowy, kopiuję kod z linku PO ( api.rubyonrails.org/classes/ActionView/Base.html ) <% if local_assigns.has_key? : nagłówek%> Nagłówek: <% = nagłówek%> <% end%> - has_key pozwala uniknąć sytuacji zerowej / fałszywej i prawdopodobnie można go skrócić do jednej linii, tak jak tutaj
Phil

5
Sprawdź odpowiedź Pablo: local_assigns.fetchdoskonale obsługuje nawet klucze o zerowej wartości. Zwraca wartość domyślną tylko wtedy, gdy klucz nie jest w ogóle ustawiony.
quetzalcoatl

158

Ponieważ local_assignsjest to skrót, możesz również użyć opcji pobierania z opcją default_value.

local_assigns.fetch :foo, default_value

Zwróci to, default_valuejeślifoo nie zostało ustawione.

OSTRZEŻENIE:

Uważaj, local_assigns.fetch :foo, default_valuekiedy default_valuejest metoda, ponieważ i tak zostanie wywołana w celu przekazania jej wynikufetch .

Jeśli masz default_valuemetodę, możesz owinąć ją w blok: local_assigns.fetch(:foo) { default_value }aby zapobiec jego wywołaniu, gdy nie jest potrzebne.


1
Warto to powiedzieć wprost: nilwartości są tutaj zachowane. Jeśli skrót zawiera :foozmapowane nil, fetchto zwróci nil. To znaczy przynajmniej na mojej wersji 1.9.3. Nie pamiętam, jak zachowało się 1.8.
quetzalcoatl

Zgadza się. Zapamiętuje mi problem, ponieważ local_assigns[:foo] || default_valuegdy foo zwróci wartość falsy, default_valuezostanie użyte zamiast tego. Zazwyczaj jest to problem dla Memoization, @some_value ||= expensive_methodjeśli metoda zwraca wartość falsy, zawsze zostanie wykonana.
Pablo Cantero,

1
Każdy, kto nie rozumie, dlaczego jest to najlepsza odpowiedź, nie używał wystarczająco długo rubinu. Bravo Pablo!
mastaBlasta

2
Optymalizacja jest następująca, więc musisz wywołać to tylko raz na górze szablonu, zamiast używać „ochrony pobierania” przy każdym użyciu zmiennej. foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
sethcall

Zastanawiałem się, dlaczego to nie zadziałało, założyłem, że również utworzyło zmienną, ale nadal musimy użyć zwróconej wartości:foo = local_assigns.fetch :foo, true
Vadorequest

84

Co powiesz na

<% foo ||= default_value %>

Mówi to: „użyj, foojeśli nie jest to zero lub prawda. W przeciwnym razie przypisz default_valuedo foo”


2
Nie jestem pewien, czy to działa, ponieważ foo nie jest zdefiniowane, chyba że jest przekazywane przez lokalny skrót.
jonnii

1
To działa, ale jeśli masz takie wartości domyślne, może to znak, że powinieneś użyć pomocnika?
psyho

2
Brak magii tutaj. Więcej zasobów na ten temat: groups.google.com/group/comp.lang.ruby/browse_thread/thread/…
hgmnz

37
Naprawdę podoba mi się ta wersja, ponieważ składnia jest tak zwarta. Myślę, że dużym minusem jest to, że oznacza to, że nie możesz przekazać wartości zero lub false jako wartości lokalnej, ponieważ zostanie ona domyślnie nadpisana.
brahn

16
@brahn, to dobra uwaga. W rzeczywistości należy tego unikać, jeśli foojest to wartość logiczna. Może słusznie mieć wartość falsei zostać przypadkowo zastąpiona default_value.
hgmnz

10

Myślę, że należy to powtórzyć tutaj (z http://api.rubyonrails.org/classes/ActionView/Base.html ):

Jeśli chcesz dowiedzieć się, czy określonej zmiennej lokalnej została przypisana wartość w konkretnym wywołaniu renderowania, musisz użyć następującego wzorca:

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

Testujesz używając zdefiniowanego? nagłówek nie będzie działać. Jest to ograniczenie implementacji.


6

W moim przypadku używam:

<% variable ||= "" %>

w mojej częściowej.
Nie mam pojęcia, czy to dobrze, ale dla mnie jest OK


To faktycznie działa całkiem dobrze. Działa dla nieokreślonego i przy przekazywaniu niljako lokalny w częściowym wywołaniu.
Joshua Pinter

3
Ach, czytając poniżej, to się nie powiedzie, jeśli variablejest to wartość logiczna i trzeba ją ustawić na false. Użyje wartości domyślnej zamiast falsewartości. KORZYSTAJ NA WŁASNE RYZYKO.
Joshua Pinter

5

Wiem, że to stary wątek, ale tutaj jest mój mały wkład: chciałbym użyć local_assigns[:foo].presencew warunkowego wewnątrz częściowe. Następnie ustawiam footylko w razie potrzeby w wywołaniu renderowania:

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

Zobacz oficjalny przewodnik po Railsach tutaj . Obowiązuje z RoR 3.1.0.


Nie widzę żadnej prawdziwej różnicy między local_assigns[:foo]i local_assigns[:foo].presence. Albo jeden zwróci, niljeśli klucz nie istnieje w haszu, a wartość, jeśli istnieje.
jamesmarkcook

1

Myślę, że lepsza opcja, która pozwala na wiele zmiennych domyślnych:

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

Jest to pochodna odpowiedzi Pabla. To pozwala mi ustawić wartość domyślną („pełny”), a na koniec „tryb” jest ustawiony zarówno w local_assigns, jak i w rzeczywistej zmiennej lokalnej.

haml / slim:

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

erb:

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

0

Bardziej intuicyjny i kompaktowy:

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


3
Myślę, że to się nie powiedzie, jeśli nazwiesz częściowe z:locals => {:some_local => false}
brahn 17.11.11

0

Jeśli nie chcesz przekazywać zmiennej lokalnej na częściową za każdym razem, gdy ją wywołujesz, wykonaj następujące czynności:

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

W ten sposób unikniesz undefined variablebłędu. Umożliwi to wywołanie częściowe z / bez zmiennych lokalnych.


LUB local_param = local_param jeśli zdefiniowano? (Local_param)
Kinaan Khan Sherwani

0

Ruby 2.5

Erb

Jest to możliwe, ale musisz zadeklarować wartości domyślne w zakresie.

ZMIENNE słowo na wymianę.

# 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

Pomocnika można utworzyć tak, aby wyglądał następująco:

somearg = opt(:somearg) { :defaultvalue }

Wdrożony jak:

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

Zobacz mojego bloga aby dowiedzieć się, jak i dlaczego.

Zauważ, że to rozwiązanie pozwala przekazać wartość zero lub fałsz jako wartość bez przesłonięcia.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.