The short answer: use a heredoc
Yes. Ruby can hold several lines in one string literal. You do not need a chain of + operators.
For a block of text that should keep its line breaks, start with a squiggly heredoc: <<~TEXT. If you only want to wrap a long line of source code, use adjacent quoted literals with a line-continuation backslash. These solve different problems, so decide what the finished string should contain before choosing the syntax.
message = <<~TEXT
Your deployment is ready.
Open the project dashboard to review it.
TEXT
p message"Your deployment is ready.\nOpen the project dashboard to review it.\n"The \n characters in this output represent real line breaks. p displays the string with escapes, which makes invisible whitespace easy to see. puts would print the two sentences on separate lines. The final newline is part of the value.
The examples in this guide were executed with Ruby 3.4.5. They use core Ruby unless a section explicitly mentions Rails.
First, choose the output you actually need
A string can span several lines in your editor and still produce a single line at runtime. It can also contain newlines even when its source fits on one line.
| Method | Best use | Whitespace and interpolation |
|---|---|---|
| <<~TEXT | Indented blocks of readable text | Common indentation removed; newlines kept; interpolation enabled |
| <<~'TEXT' | Literal text containing #{...} or backslashes | Common indentation removed; interpolation disabled |
| <<-TEXT | Keep leading spaces but indent the closing marker | Content indentation kept; interpolation enabled |
| Adjacent literals with \ | Wrap a long source line without output newlines | Include word spaces yourself; interpolation follows quote type |
| %q{...} / %Q{...} | Text containing many quote characters | Line breaks kept; %q is literal, %Q interpolates |
| Ordinary quoted string | Short text with a few lines | Physical line breaks become part of the string |
For an email body, preserved line breaks usually make sense. For a log label or a sentence shown beside a button, they may not. For a configuration file, leading spaces may carry meaning. Choosing around the output keeps formatting fixes from quietly changing the data.
Write readable blocks with <<~
A heredoc gives a longer string its own space in the source file. A descriptive closing marker makes the boundary easy to scan.
def deployment_note
<<~NOTE
Release checklist:
Verify the health endpoint.
Review the error log.
NOTE
end
p deployment_note"Release checklist:\n Verify the health endpoint.\n Review the error log.\n"Both checklist items still have two leading spaces. This matters when your text includes a nested list or a deliberately indented section. In code review, read the least-indented nonblank line first: it gives you a useful visual reference for the shared indentation that will disappear.
Choose a marker such as EMAIL_BODY, QUERY or RELEASE_NOTE rather than a generic label in every case. The name does not affect the output; it helps the next developer understand the text. Use a marker that will not also appear alone inside the body.
Understand <<, <<- and <<~
The three heredoc forms differ in how they treat indentation. A misplaced closing marker can also make otherwise simple code fail to parse.
text = <<-TEXT
Keep these two leading spaces.
TEXT
p text" Keep these two leading spaces.\n"Use plain <<TEXT when the terminator belongs at the beginning of a line. <<-TEXT lets you indent that terminator. <<~TEXT also handles shared body indentation. Keep the closing marker on its own line, spell it exactly as you opened it, and avoid trailing spaces after it.
If Ruby reports an unexpected end of file after you add a heredoc, inspect the closing marker before rewriting the surrounding method. A typo, an accidental suffix or a marker that has been swallowed by the body is often easier to fix than the error location suggests.
Choose interpolation deliberately
A name, a date or a small calculated value can be inserted with #{...}. Keep larger transformations outside the string so the text remains easy to review.
project = "Client portal"
message = <<~EMAIL
Project: #{project}
Next step: review the release notes.
EMAIL
p message"Project: Client portal\nNext step: review the release notes.\n"text = <<~'TEXT'
The template contains #{project}.
The characters \n stay visible.
TEXT
p text"The template contains \#{project}.\nThe characters \\n stay visible.\n"The second form is useful for documenting a template that contains Ruby interpolation syntax. It avoids looking up project at all. A reviewer can see that the text is intended to be copied as written, rather than assembled from runtime values.
Interpolation is formatting, not validation. When a string is later used as SQL, HTML or a shell command, the rules of that destination still apply. Keep database values in the adapter’s bound parameters; escape output for its HTML context; pass command arguments separately rather than assembling a shell command from user input.
Wrap the source without adding output newlines
For a long sentence that must stay on one line, adjacent literals are often clearer than making a multiline block and cleaning it afterwards.
message = "Your project is ready for review. " \
"Open the dashboard when you have a moment."
p message"Your project is ready for review. Open the dashboard when you have a moment."The space after the first sentence is inside the quotes. Without it, the result would join the full stop directly to Open. Keep the backslash as the final character on its source line; do not put a comment after it.
There is no explicit + in this example, although Ruby still combines the adjacent literals. This is useful for fixed text. It is not a replacement for joining an array of values that you only know at runtime.
steps = ["Design", "Build", "Review"]
p steps.join(" / ")"Design / Build / Review"Ordinary quotes and percent strings also work
You do not have to use a heredoc for every short block. Choose the form that leaves the text easiest to read.
text = "Build complete.
Review pending."
p text"Build complete.\nReview pending."project = "Portal"
literal = %q{The label is "#{project}".}
interpolated = %Q{The label is "#{project}".}
p literal
p interpolated"The label is \"\#{project}\"."
"The label is \"Portal\"."Here, %q keeps the interpolation expression visible, while %Q inserts the value. The braces are delimiters, not part of the finished text. Percent strings are useful when the body contains many quote marks; they do not automatically remove newlines or tidy indentation.
Avoid switching syntax just to save two escaped quotes if the result is harder for your team to read. Consistency around a small set of patterns is usually more useful than demonstrating every form Ruby supports.
Remove only the whitespace you mean to remove
A cleanup method should express a specific requirement. Removing the final newline and trimming all outer whitespace are different operations.
message = <<~TEXT.chomp
Build complete.
Review pending.
TEXT
p message"Build complete.\nReview pending."text = " keep my indentation \n"
p text.chomp
p text.strip" keep my indentation "
"keep my indentation"For a comparison in a test, first write the exact string you expect. If the value differs only at the end, inspect the final character rather than adding .strip everywhere. Overly broad cleanup can hide a formatting mistake in one place and remove meaningful spaces in another.
Rails squish is a separate choice
Some answers about Ruby recommend squish. That method belongs to Active Support, so a standalone Ruby process does not get it simply because it supports strings.
In a Rails application, String#squish can be convenient for a sentence whose whitespace should collapse to single spaces. It also removes whitespace at the beginning and end. Use it for that intended transformation, not as a general repair for a heredoc.
Outside Rails, you would need the Active Support dependency and its string filters extension. Do not add a framework dependency solely to wrap a fixed sentence: the adjacent-literal example above already solves that case in core Ruby.
text = "Account: North America\nStatus: ready"
p text.gsub(/\s+/, " ")"Account: North America Status: ready"The double space in the region name and the line separating the fields have both disappeared. That may be fine for a display label, but it is a change in content. SQL comments, quoted values and whitespace-sensitive formats deserve particular care. Test the finished value against the format you are producing.
Common mistakes worth catching in review
Most bugs here come from invisible characters or from confusing two different uses of the same punctuation.
- Missing spaces between adjacent literals: inspect the boundary between the two pieces.
- Assuming <<- removes indentation: use <<~ when shared indentation should be removed.
- Using .strip when only the final newline is unwanted: check whether .chomp expresses the requirement.
- Expecting %q to interpolate: use %Q when runtime values belong in the text.
- Calling .squish in plain Ruby without Active Support: distinguish language features from framework methods.
- Treating Ruby triple quotes like Python triple quotes: Ruby does not have a separate triple-quoted string syntax.
Also distinguish a heredoc from String#<<. In a heredoc, <<TEXT begins a literal block. When applied to a string object, << appends to that object. If the receiver is frozen, an append will fail; changing the quoting style does not make the mutation safe.
buffer = String.new("Build")
buffer << " complete"
p buffer
begin
"Build".freeze << " complete"
rescue FrozenError => error
puts error.class
end"Build complete"
FrozenErrorVerify the value, not just the formatting
Before merging a change, test the string as the next system will receive it. A neat block in your editor is only half of the job.
actual = <<~TEXT.chomp
Release ready.
Review the dashboard.
TEXT
expected = "Release ready.\n Review the dashboard."
raise "Unexpected message formatting" unless actual == expected
puts "Formatting check passed"Formatting check passedThis small assertion protects both the two-space indent and the absence of a final newline. In an application test, also cover the values you interpolate: an empty name, non-ASCII text or a value containing punctuation may expose assumptions that a fixed example does not.
For an email, inspect the plain-text body as well as the rendered message. For a generated file, parse it with the consumer’s parser where possible. For a database query, use the adapter’s parameter binding and test through the database layer. Let the destination decide what correctness means.
Where this question comes from
The long-running Stack Overflow discussion is a useful record of the different ways developers approach the problem. This guide uses new examples and separates the output requirements before recommending a method.
Stack Overflow: Can I write a multi-line string with no concatenation?
Official Ruby documentation: string and heredoc literals
When reading an older answer, check the language version and whether the solution assumes Rails. Then run the example with your own input. A recommendation that is right for a short display sentence may be wrong for a file body that must preserve every line break.
Working on a Ruby project?
If your question is part of a larger Ruby application, integration or modernization project, we would be happy to discuss the work with you.
Bring a short description of the system, the behavior you want to change and any constraints around deployment or existing integrations. We can discuss the scope and the next practical step without asking you to settle the entire architecture in advance.