Ruby
Heredoc

Ruby におけるヒアドキュメント(行指向文字列リテラル)の基本的な仕様

前提

  • ヒアドキュメントという言葉すら知らないケツの青い少年です。

環境

  • ruby 2.5.0

TL;DR

<<[(-|~)]["'`]識別子["'`]
   ...
識別子

詳細

ヒアドキュメントをざっくり言うと、複数行にまたがる文字列を出力したいときに使う文法です。よく見るのは、長くなりがちな生 SQL を書く時とか、マークダウンの出力結果をテストする時とかに使うって感じでしょうか。たぶんもっとたくさん使い道あると思います。

<<FOO
  <div class="foo">
    <p>Hello World!</p>
  </div>
FOO
# => "  <div class=\"foo\">\n    <p>Hello World!</p>\n  </div>\n"

<<SQL
  select count(*) from users where created_at between '1970-01-01' and '2018-10-11'
SQL
# => "  select count(*) from users where created_at between '1970-01-01' and '2018-10-11'\n"

各パターンの特徴

TL;DR に書いたように、いくつか書き方のパターンがあるので、それぞれのパターンの特徴を列挙していきたいと思います。

<<<<-<<~

<<FOO
FOO
  • 中身のコードのインデントは省略されず、そのまま出力されます。
  • 終端の FOO にインデントは許可されていません。必ず行頭にべったりくっついてる必要があります。

<<-FOO
FOO

# 例)
def foo
  var = <<-FOO
    some_variable
  FOO
  # ↑ インデントしていてもOK!
end
  • 中身のコードのインデントは省略されず、そのまま出力されます。
  • 終端の FOO はインデントすることができます。つまり、上記の例のように、ネストしたコードの中に書いてもOKです。

<<~FOO
FOO

# 例)
foo = <<~FOO
                                    hoge
                                      hoge
                                        hoge
                                          hoge
FOO

puts foo
# ながーいインデントが省略されます。
hoge
  hoge
    hoge
      hoge
=> nil
  • 一番インデントの短い行を基準として、全ての行のインデントが省略されます。
  • 終端の FOO はインデントすることができます。つまり、上記の例のように、ネストしたコードの中に書いてもOKです。

<<"FOO"<<'FOO'<<`FOO`

<<"FOO"
FOO

# 例)
<<"FOO"
  昨日のこの時間は、 #{1.day.ago} です。
FOO
# => "  昨日のこの時間は、 2018-10-11 00:32:59 +0900 です。\n"
  • 識別子(FOO)で囲まれたコードは "" で囲まれた文字列と同じ扱いになります。つまり、 #{} を使って式展開することができます。
  • ちなみに <<FOO のように何も囲わなかった場合、ダブルクオートで囲まれたのと同じ扱いになります。

<<'FOO'
FOO

# 例)
<<'FOO'
  hoge\n  hoge\n    hoge\n      hoge\n
FOO
=> "  hoge\\n  hoge\\n    hoge\\n      hoge\\n\n"
  • 識別子(FOO)で囲まれたコードは '' で囲まれた文字列と同じ扱いになります。つまり、書いた文字列がそのまま出力されます。逆にこの場合、 #{} による式展開は無視されます。

<<`FOO`
FOO

# 例)
<<`FOO`
  echo #{Time.current}
FOO
# => "2018-10-12 00:08:53 +0900\n"
  • 識別子(FOO)で囲まれたコードは `` で囲まれた文字列と同じ扱いになります。つまり、中身のコードはコマンドとして実行され、結果の標準出力が文字列として返されます。なお、コマンド実行の前に式展開が行われるので、 #{} を使うことができます。

引数としてのヒアドキュメント

メソッドの引数に開始ラベルを書くことによって、ヒアドキュメント全体を引数として代入することができます。以下の例の場合、生 SQL を第1引数として find_by_sql メソッドに渡しています。

users_in_first_quarter = Users.find_by_sql([<<~SQL, { from: '2018-01-01', to: '2018-03-31' }])
  select count(*) as number_of_users from users where created_at between :from and :to
SQL

puts users_in_first_quarter.number_of_users

複数のヒアドキュメント

(2018/10/12追記)
以下のように、複数のヒアドキュメントを重ねて書くこともできます。仮に変数に代入した場合は、配列として保存されます。

foo = <<~FOO, <<~BAR, <<~BAZ
  this is foo
FOO
  this is bar
BAR
  this is baz
BAZ
# => ["this is foo\n", "this is bar\n", "this is baz\n"]

参考文献

公式ドキュメント is 最高。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html