今天我们来探索一下content_tag
方法。
这个方法很重要,因为 ActionView 中很多方法,比如我们之前提到的link_to
,最终都会使用此方法来生成 html 元素。
在开始之前,我们可以想像,content_tag
在执行过程中肯定是首先用字符串的形式,拼接起了一个 html 元素,比如'<a href="http://example.com"></a>'
,然后将其转化为页面上的 html。
了解 content_tag
的用法
从文档中可以看出content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
接受5个参数,其中最后一个是代码块。而第二个参数叫content_or_options_with_block
,结合文档理解这个参数名的意思是:如果你传入了 block 那么这个参数被用作 options,反之则是 content。
那么下面举几个例子:
1 | content_tag(:p, '1st line', class: 'my-class', id: 'my-id') |
根据我们之前在了解link_to
时得到的经验,content_tag
方法也一定会根据是否传入了 code block 来决定是否转换传入的参数。从content_tag
的源代码可以看出:
1 | def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) |
被转换的参数就是我们刚刚提到的第二个参数content_or_options_with_block
,他会被传给原本的第三个参数options
。这里有一个有意思的事情,Rails 不会处理escape
参数的位置,也就是说,如果你想传入 escape,你需要把 escape 作为第 4 个参数,而不能作为第 3 个参数,然后等着 Rails 帮你挪到第 4 位。所以,就如上面的例子中的最后一个一样,你需要给用一个占位的值,作为第三个参数:
1 | content_tag(:p, {class: 'my-class', id: 'my-id'}, nil, false) { some_code } |
一旦这些参数被传入,content_tag
做的事情其实很简单,把他们整理之后传入一个叫content_tag_string
的方法。而这个新出现的方法在做的事情,就是我们在开始提到的,拼接一个可以被编译为 html 元素的 string。
在我们开始探索接下来的content_tag_string
方法之前,让我们来看看content_tag
方法是怎么整理参数的:
- 如果没有 code block,那么直接原样传入
content_tag_string
- 如果有 code block,不仅将之前说的
content_or_options_with_block
传给options
,并且把capture(&block)
作为content_tag_string
的第二个参数。
这里我们可以看看capture
是什么:1
2
3
4
5
6
7def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
if (string = buffer.presence || value) && string.is_a?(String)
ERB::Util.html_escape string
end
end
按照文档的说明,这个方法将传入的页面模版转化为 String 对象,这样如果把 capture 的返回结果赋值给一个变量,它就可以用在页面的任何地方,而不用重新写 Template。比如这样的例子:
1 | @greeting = capture {"Hello World at #{Time.now}"} |
而后你就可以把@greeting
用在页面的任何地方,比如:
1 | <title><%= @greeting %></title> |
实际上,文档中所说的这个 String 对象其实是个ActiveSupport::SafeBuffer
对象,但其父类确实是String
。这个对象是由 ERB::Util.html_escape
方法返回的。不过我们这里暂时不去探索 ERB::Util.html_escape
了,但可以想像,其内部是在处理字符,转换一些编码(escape)。
content_tag_string
方法
content_tag_string
方法其实是TagBuilder
这个类的一个实例方法,在content_tag
中,首先利用 tag_builder
这个方法构造了一个TagBuilder
的实例,而后调用content_tag_string
方法:
1 | def content_tag_string(name, content, options, escape = true) |
最后,这个方法把 tag_options 和其他字符拼在一起,形成一个新的 String 对象(其实还是ActiveSupport::SafeBuffer
对象)。这个对象也就是content_tag
方法的返回值。