необязательные локальные переменные в частичных шаблонах rails: как мне выйти из (определенного? foo) беспорядка?

225

Я был плохим парнем и использовал следующий синтаксис в моих частичных шаблонах, чтобы установить значения по умолчанию для локальных переменных, если значение не было явно определено в хеше: locals при рендеринге частичного -

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

Казалось, это работало нормально до недавнего времени, когда (без какой-либо причины) непропущенные переменные начали вести себя так, как если бы они были определены как nil (а не как undefined).

Как указывалось различными полезными людьми на SO, http://api.rubyonrails.org/classes/ActionView/Base.html говорит, что не следует использовать

defined? foo

и вместо того, чтобы использовать

local_assigns.has_key? :foo

Я пытаюсь изменить свои способы, но это означает изменение большого количества шаблонов.

Можно / нужно просто зарядить заранее и внести это изменение во все шаблоны? Есть ли какая-то хитрость, за которой мне нужно следить? Насколько усердно мне нужно проверять каждого?

Brahn
источник

Ответы:

324

Я сделаю это:

<% some_local = default_value if local_assigns[:some_local].nil? %>
jonnii
источник
1
Хотя мне действительно нравится компактный синтаксис предложения hgimenez (выше), этот подход имеет то преимущество, что он очень ясен: что происходит. (У него по-прежнему есть недостаток - не разрешать вам передавать ноль в качестве значения для местного населения)
Brahn
1
Какой у вас сценарий использования для передачи нуля?
Джонни
О, я не имею в виду конкретный случай. Просто пытаюсь понять все последствия. Это напомнило мне принять этот ответ :-)
Brahn
4
Чтобы обойти ноль, я копирую код по ссылке ОП ( api.rubyonrails.org/classes/ActionView/Base.html ) <% if local_assigns.has_key? : headline%> Заголовок: <% = заголовок%> <% end%> - has_key избегает ситуации ноль / ложь и, вероятно, может быть сокращен до одной строки, как ответ здесь
Фил
5
Пожалуйста, проверьте ответ Пабло: local_assigns.fetchотлично обрабатывает даже ключи с нулевым значением. Он возвращает значение по умолчанию, только если ключ не установлен вообще.
Кетцалькоатль
158

Поскольку local_assignsэто хеш, вы также можете использовать fetch с опциональным default_value.

local_assigns.fetch :foo, default_value

Это вернется, default_valueесли fooне было установлено.

ПРЕДУПРЕЖДЕНИЕ:

Будьте осторожны с методом local_assigns.fetch :foo, default_valuewhen default_value, так как он все равно будет вызван, чтобы передать его результат fetch.

Если ваш default_valueметод, вы можете заключить его в блок: local_assigns.fetch(:foo) { default_value }для предотвращения его вызова, когда он не нужен.

Пабло Кантеро
источник
1
Стоит сказать об этом явно: nilздесь сохраняются значения. Если хеш содержит :fooсопоставленный nil, то fetchон вернется nil. То есть по крайней мере на моем v1.9.3. Я не помню, как вела себя 1.8.
Кетцалькоатль
Это совершенно верно. Он помнит проблему local_assigns[:foo] || default_value, когда foo возвращает ложное значение, default_valueвместо него будет использоваться. Обычно для Мемоизации возникает проблема, @some_value ||= expensive_methodесли метод возвращает ложное значение, оно всегда будет выполняться.
Пабло Кантеро
1
Тот, кто не понимает, почему это лучший ответ, не использовал рубин достаточно долго. Браво Пабло!
mastaBlasta
2
Оптимизация заключается в следующем, так что вам нужно вызывать его только один раз в верхней части шаблона, вместо того, чтобы использовать «fetch guard» при каждом использовании переменной. foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
sethcall
Мне было интересно, почему это не сработало, я предположил, что он тоже создал переменную, но мы все еще должны использовать возвращаемое значение:foo = local_assigns.fetch :foo, true
Vadorequest
84

Как насчет

<% foo ||= default_value %>

Это говорит "используйте, fooесли это не ноль или истина. В противном случае назначьтеdefault_value foo"

hgmnz
источник
2
Я не уверен, что это работает, поскольку foo не определен, если он не передан через локальный хеш.
Джонни
1
Это работает, но если у вас есть значения по умолчанию, как это, может быть, это признак того, что вы должны использовать помощник?
Психо
2
Никакой магии здесь. Дополнительные ресурсы по теме: groups.google.com/group/comp.lang.ruby/browse_thread/thread/…
hgmnz
37
Мне очень нравится эта версия, так как синтаксис такой компактный. Я предполагаю, что большим недостатком является то, что это означает, что вы не можете передать nil или false в качестве значения для локального, поскольку оно будет перезаписано по умолчанию.
Бран
16
@Brahn, это хороший момент. На самом деле, этого следует избегать, если fooэто логическое значение. Он может по праву иметь значение falseи может быть default_valueслучайно изменен .
hgmnz
10

Я думаю, что это должно быть повторено здесь (с http://api.rubyonrails.org/classes/ActionView/Base.html ):

Если вам нужно выяснить, было ли определенной локальной переменной присвоено значение в конкретном вызове рендеринга, вам нужно использовать следующий шаблон:

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

Тестирование с использованием определенных? заголовок не будет работать. Это ограничение реализации.

Гамов
источник
6

В моем случае я использую:

<% variable ||= "" %>

в моем частичном.
Я понятия не имею, если это хорошо, но для меня это нормально

Моизес Портильо
источник
Это на самом деле работает довольно хорошо. Работает для неопределенных и при передаче nilкак локальный в частичном вызове.
Джошуа Пинтер
3
Ах, читая ниже, это потерпит неудачу, если variableэто логическое значение, и вам нужно установить его false. Он будет использовать значение по умолчанию вместо использования falseзначения. ИСПОЛЬЗУЙТЕ НА СВОЙ РИСК.
Джошуа Пинтер
5

Я знаю, что это старая ветка, но вот мой маленький вклад: я бы использовал local_assigns[:foo].presenceв условной части в частичной. Затем я устанавливаю fooтолько при необходимости в вызове рендеринга:

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

Взгляните на официальный путеводитель по Rails здесь . Действительно с RoR 3.1.0.

microspino
источник
Я не вижу никакой реальной разницы между local_assigns[:foo]и local_assigns[:foo].presence. Любой из них вернется, nilесли ключ не существует в хэше, и значение, если оно существует.
jamesmarkcook
1

Я думаю, что лучший вариант, который учитывает несколько переменных по умолчанию:

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

Это производная от ответа Пабло. Это позволяет мне установить значение по умолчанию ('full'), и, в конце, 'mode' устанавливается как в local_assigns, так и в реальной локальной переменной.

Haml / тонкий:

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

Еврорадио:

<% mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full') %>
sethcall
источник
0

Более интуитивно понятный и компактный:

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

muirbot
источник
3
Я думаю, что это не удастся, если вы позвоните в частичную с:locals => {:some_local => false}
Brahn
0

Если вы не хотите передавать локальную переменную в частичную каждый раз, когда вы вызываете ее, вы делаете это:

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

Таким образом вы избежите undefined variableошибки. Это позволит вам вызвать ваш частичный с / без локальных переменных.

Харис Краина
источник
ИЛИ local_param = local_param, если он определен? (Local_param)
Кинаан Хан Шервани
0

Ruby 2.5

эрб

Это возможно, но вы должны объявить значения по умолчанию в области видимости.

ПЕРЕМЕННОЕ слово для замены.

# 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
источник
-6

Помощник может быть создан, чтобы выглядеть так:

somearg = opt(:somearg) { :defaultvalue }

Реализовано так:

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

Смотрите мой блог для деталей о том, как и почему.

Обратите внимание, что это решение позволяет вам передавать nil или false в качестве значения без переопределения.

Хайме Чам
источник