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?

Brahn
źródło

Odpowiedzi:

324

Robię to:

<% some_local = default_value if local_assigns[:some_local].nil? %>
jonnii
źródło
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.

Pablo Cantero
źródło
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”

hgmnz
źródło
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.

gamov
źródło
6

W moim przypadku używam:

<% variable ||= "" %>

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

Moises Portillo
źródło
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.

microspino
źródło
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] %>
Daniel OCallaghan
źródło
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') %>
sethcall
źródło
0

Bardziej intuicyjny i kompaktowy:

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

muirbot
źródło
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.

Haris Krajina
źródło
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 %>
...
dimpiax
źródło
-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.

Jaime Cham
źródło