Ruby: More about ruby hearedoc – questions and answers

There are two separate Ruby ideas here, and both are worth understanding properly.

1. Ruby Heredoc: <<, <<-, and <<~

A heredoc is Ruby’s syntax for defining a multiline string.

The basic structure is:

variable = <<IDENTIFIER
some
multiline
text
IDENTIFIER

The identifier (TEXT, SQL, HTML, JSON, etc.) is not special. It is simply a marker that tells Ruby:

“The string starts here and ends when you encounter this exact marker.”

For example:

message = <<TEXT
Hello
World
TEXT

The resulting string is:

Hello
World

The interesting part is what happens to the closing marker’s indentation and the indentation of the content.


2. <<TEXT – Traditional Heredoc

Consider:

message = <<TEXT
Hello
World
TEXT

The closing TEXT must start at the beginning of the line.

This works:

def message
<<TEXT
Hello
World
TEXT
end

But this does not work:

def message
<<TEXT
Hello
World
TEXT
end

Because Ruby expects:

TEXT

at the beginning of the line.

Why?

Ruby needs to recognize the terminating identifier.

Think of:

<<TEXT

as saying:

“Keep reading source code until you find a line containing TEXT in the required position.”

So:

TEXT

is the terminator.

But:

  TEXT

is not the terminator for the normal <<TEXT form.


3. Why is this inconvenient?

Ruby style normally uses indentation.

For example:

class UserMailer
def welcome_email
body = <<TEXT
Hello
Welcome to our application.
Thank you.
TEXT
body
end
end

Notice how ugly this is.

The Ruby code is indented:

class
def
body =

but the heredoc terminator suddenly jumps all the way to column 1:

TEXT

This is where <<- becomes useful.


4. <<-TEXT – Allow an Indented Terminator

Now:

message = <<-TEXT
Hello
World
TEXT

Ruby allows the closing identifier to be indented.

So this is valid:

def message
<<-TEXT
Hello
World
TEXT
end

The important thing is:

<<- changes how Ruby recognizes the terminating marker.

It does not remove indentation from the actual content.

For example:

message = <<-TEXT
Hello
World
TEXT

The resulting string still contains the spaces before Hello and World.

Conceptually:

" Hello\n World\n"

The - means:

“I’m allowing the terminator to appear with indentation.”

It does not mean:

“Remove indentation.”

That’s the major distinction.


5. <<~TEXT – Squiggly Heredoc

Ruby later introduced the squiggly heredoc:

message = <<~TEXT
Hello
World
TEXT

Now Ruby does two things:

  1. Allows the terminator to be indented.
  2. Removes common leading indentation from the content.

This is why <<~ is extremely useful in modern Ruby/Rails code.


6. Understanding “common indentation”

This part is slightly more subtle.

Consider:

message = <<~TEXT
Hello
World
Ruby
TEXT

Every line has two spaces:

··Hello
··World
··Ruby

<<~ removes those two common spaces.

Result:

Hello
World
Ruby

So:

puts message

prints:

Hello
World
Ruby

7. What if the lines have different indentation?

This is where “common indentation” becomes important.

Consider:

message = <<~TEXT
Hello
World
Ruby
TEXT

The source indentation is:

··Hello
····World
··Ruby

The smallest/common indentation is 2 spaces.

Ruby removes those two spaces from every relevant line.

Result:

Hello
World
Ruby

So <<~ does not flatten all indentation.

It removes the indentation that is common to the content.

That’s exactly what we want when writing nested code.


8. Why <<~ is better for Ruby source code

Consider:

class UserMailer
def welcome_email
body = <<~TEXT
Hello #{user.name},
Welcome to our application.
Regards,
The Team
TEXT
body
end
end

The source code remains nicely formatted.

The Ruby indentation:

class
def
body
Hello

doesn’t become unwanted indentation in the actual string.

The resulting string is:

Hello John,
Welcome to our application.
Regards,
The Team

That’s the main reason <<~ is so popular.


9. <<- vs <<~: The Critical Difference

This is probably the most important thing to remember.

<<-

text = <<-TEXT
Hello
World
TEXT

Means:

“Allow the closing TEXT to be indented.”

It doesn’t clean up content indentation.


<<~

text = <<~TEXT
Hello
World
TEXT

Means:

“Allow the closing TEXT to be indented, and remove common indentation from the content.”


<<

text = <<TEXT
Hello
World
TEXT

Means:

“The terminator must appear without indentation.”

A useful mental model:

             Terminator     Content indentation
<<TEXT       strict         preserved
<<-TEXT      flexible       preserved
<<~TEXT      flexible       normalized

10. What about interpolation?

All of these are still normal Ruby strings, so interpolation works.

name = "Abhilash"
message = <<~TEXT
Hello #{name},
Welcome to Ruby.
TEXT

Result:

Hello Abhilash,
Welcome to Ruby.

You can also execute Ruby expressions:

message = <<~TEXT
User: #{user.name}
Email: #{user.email}
Status: #{user.active? ? "Active" : "Inactive"}
TEXT

This is one reason heredocs are particularly useful in Rails applications.


11. You can use different terminator names

The terminator doesn’t have to be TEXT.

These are all valid:

<<~TEXT
...
TEXT
<<~SQL
...
SQL
<<~HTML
...
HTML
<<~JSON
...
JSON
<<~RUBY
...
RUBY

The name is simply a marker.

Using a meaningful marker improves readability:

sql = <<~SQL
SELECT *
FROM users
WHERE active = true
SQL

is much clearer than:

sql = <<~TEXT
SELECT *
FROM users
WHERE active = true
TEXT

12. A Very Useful Rails Example

You may see this frequently in Rails code:

sql = <<~SQL
SELECT users.id, users.email
FROM users
INNER JOIN orders ON orders.user_id = users.id
WHERE users.active = TRUE
GROUP BY users.id, users.email
SQL

Without <<~, you would have to deal with the terminator’s indentation.

With <<~, the SQL can visually belong to the Ruby code while still producing clean SQL.

This is also useful for:

render inline: <<~HTML
<div class="user">
<h2>#{user.name}</h2>
</div>
HTML

and:

command = <<~BASH
bundle install
bundle exec rails db:migrate
bundle exec rails server
BASH

The two things worth remembering for ints

Heredocs:

<< # strict terminator
<<- # indented terminator allowed
<<~ # indented terminator + dedent content

One subtle point: <<~ does not simply call strip on every line. It calculates the indentation that can be removed from the heredoc content and removes that common indentation while preserving deeper relative indentation. That distinction is important when you’re using heredocs for HTML, SQL, YAML, or nested text.