Rails局部模板中的可选局部变量:如何摆脱(已定义?foo)混乱?


224

我一直是个坏孩子,如果在渲染局部变量时未在:locals哈希中明确定义值,则在局部模板中使用以下语法设置局部变量的默认值-

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

直到最近,这(似乎我无法辨别)未通过的变量开始表现出来就好像它们已被定义为nil(而不是未定义)一样,似乎一直很好。

正如SO的各种乐于助人的人指出的那样,http//api.rubyonrails.org/classes/ActionView/Base.html不要使用

defined? foo

而是使用

local_assigns.has_key? :foo

我正在尝试修改自己的方式,但这意味着要更改很多模板。

我可以/应该提前收费并在所有模板中进行此更改吗?我需要注意什么技巧吗?我需要多么努力地测试每一个?

Answers:


323

我这样做:

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

1
尽管我真的很喜欢hgimenez建议的紧凑语法(上面),但是这种方法的优点是非常清楚:发生了什么。(仍然有一个缺点,就是不允许您将nil作为本地值传递)
brahn 2010年

1
您想传递nil的用例是什么?
jonnii 2010年

哦,我没有特别的想法。只是想了解全部含义。这提醒我接受这个答案:-)
brahn 2010年

4
为了解决零问题,我从OP的链接(api.rubyonrails.org/classes/ActionView/Base.html)复制代码<%if local_assigns.has_key?:headline%>标题:<%=标题%> <%结束%>-has_key避免了nil / false的情况,并且可以缩短为一行,就像这里的答案
Phil

5
请检查Pablo的答案:local_assigns.fetch完美处理nil值的键。仅在根本没有设置密钥的情况下,它才返回默认值。
quetzalcoatl

157

由于local_assigns是哈希,因此您还可以将fetch与optional一起使用default_value

local_assigns.fetch :foo, default_value

default_value如果foo未设置,它将返回。

警告:

请注意local_assigns.fetch :foo, default_value什么时候default_value是方法,因为无论如何都会调用它以将其结果传递给fetch

如果您default_value是一个方法,则可以将其包装在一个块中:local_assigns.fetch(:foo) { default_value }防止在不需要时调用它。


1
值得明确地说:nil值保留在这里。如果散列包含:foo映射到nil,则fetch它将返回nil。也就是说,至少在我的v1.9.3上。我不记得1.8的表现如何。
quetzalcoatl

没错 它记得我的问题local_assigns[:foo] || default_value,当foo返回一个假值时,default_value将使用代替。@some_value ||= expensive_method如果该方法返回一个伪造的值,通常对于记忆是一个问题,它将始终被执行。
Pablo Cantero 2013年

1
任何不理解为什么这是最佳答案的人都没有足够长时间使用红宝石。喝彩!
mastaBlasta 2014年

2
下面是一个优化,因此您只需要在模板顶部调用一次即可,而不必在每次使用变量时都使用“获取防护”。 foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
sethcall

我想知道为什么它不起作用,我以为它也创建了变量,但是我们仍然必须使用返回的值:foo = local_assigns.fetch :foo, true
Vadorequest 2014年

84

怎么样

<% foo ||= default_value %>

这说:“ foo如果不是nil或true,请使用。否则分配default_value给foo”


2
我不确定这是因为没有定义foo,除非它通过本地哈希值传递。
jonnii 2010年

1
这行得通,但是如果您具有这样的默认值,也许这表明您应该使用帮助程序?
psyho 2010年

2
这里没有魔术。:关于这个问题的更多资源groups.google.com/group/comp.lang.ruby/browse_thread/thread/...
hgmnz

37
我真的很喜欢这个版本,因为语法是如此紧凑。我认为最大的缺点是这意味着您不能将nil或false作为本地值传递,因为默认情况下它将被覆盖。
brahn 2010年

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起有效。


我看不到local_assigns[:foo]和之间的任何真正区别local_assigns[:foo].presencenil如果键在哈希中不存在,则返回任一值;如果键存在,则返回值。
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”),最后,在local_assigns和实际的本地变量中都设置了“ mode”。

雾/苗条:

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

erb:

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

0

更直观,更紧凑:

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


3
我认为,如果您致电“ :locals => {:some_local => false}
brahn”,

0

如果不想每次调用都将局部变量传递给partial,请执行以下操作:

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

这样可以避免undefined variable错误。这将允许您使用/不使用局部变量来调用局部变量。


或local_param =如果定义了local_param?(local_param)
Kinaan Khan Sherwani

0

红宝石2.5

b

有可能,但是您必须在范围中声明默认值。

字进行更换。

# 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

可以创建一个帮助器,如下所示:

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作为值而不会被覆盖。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.