So I've been going through the very poor documentation that exists for ActionView, particularly a method called select_tag
that just exists when called from view files. Supposedly, it's called like this:
select_tag name, option_tags, options
The documentation says nothing at all about the name
, very little about the options
, and describes the option_tags
only through examples that treat it as an opaque value that must be obtained from other functions.
As always, the only way to learn anything about Rails is to reverse-engineer it.
So I tried running it directly from a Rails console, which is tricky because Ruby doesn't let you call methods that are defined in modules unless you create a class and an object first:
class H
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::FormTagHelper
end
H.new.options_for_select ["foo","bar"]
The above usage of options_for_select
comes from actual code that somebody else wrote. The return value is a string:
"<option value=\"foo\">foo</option>\n<option value=\"bar\">bar</option>"
So apparently, you're supposed to pass the return value from option_for_select
(or one of the many other related functions that introduce complications I don't want to talk about such as generating HTML tags from ActiveRecord objects) as the option_tag
parameter of select_tag
. Except if you copy that string to your clipboard and paste it directly into a function call, it doesn't do what you'd expect:
H.new.select_tag :my_name, "<option value=\"foo\">foo</option>\n<option value=\"bar\">bar</option>"
Return value:
"<select name=\"my_name\" id=\"my_name\"><option value="foo">foo</option>\n<option value="bar">bar</option></select>"
At least this reveals what the name
parameter is for.
Even weirder, the text is not escaped if you pass the return value directly to select_tag
without letting it print on the console:
H.new.select_tag :name, H.new.options_for_select(["foo","bar"])
Return value:
"<select name=\"name\" id=\"name\"><option value=\"foo\">foo</option>\n<option value=\"bar\">bar</option></select>"
WTF is going on here?
In the course of writing this question, I stumbled on its answer: Ruby has been lying to me (like it always does).
When you evaluate:
H.new.options_for_select ["foo","bar"]
Ruby tells you that the result was a String. But that's only because Pry and Irb both silently call .to_s
on everything, and the thing that gets returned from options_for_select
has a to_s
. The truth:
(H.new.options_for_select ["foo","bar"]).class
=> ActiveSupport::SafeBuffer
ActiveSupport::SafeBuffer.new("<foo>")
=> "<foo>"
So whoever wrote these methods assumed that you want to incorporate raw, user-provided strings into your <select>
tags, and those strings could contain attempts at HTML/JavaScript injection, so they must be escaped.
ActiveView treats all strings as suspect, but it is possible to mark certain strings as "safe" by wrapping them in an ActiveSupport::SafeBuffer
.