Usually, in ruby the simpliest way of holding pretty formatted multiline strings сonsists in usage of string concatenation, let’s look on a simple sql query :

sql_query = 'select attr1, attr2, attr3, attr4, attr5, attr6, attr7 ' +
            'from table1, table2, table3, etc, etc, etc, etc, etc, ' +
            'where etc etc etc etc etc etc etc etc etc etc etc etc etc'

We can indent this code how we want and place it where we want and it have as much spaces as we want - not more, not less. But this method has one crucial disadvantage. Consider having a very-long-long-line-text. So, in accordance to this method we need to split text line by line and join it with quotes and plus. This doesn’t seem to be a good way. Let’s try something else.

Ok, the other way of formatting this query consists in in-place raw query usage, we had to start from this method :

sql_query = 'select attr1, attr2, attr3, attr4, attr5, attr6, attr7
             from table1, table2, table3, etc, etc, etc, etc, etc,
             where etc etc etc etc etc etc etc etc etc etc etc etc etc'

Hmm, something what we want! But what can we do with this leading spaces? They are not necessary and sometimes they could ruin code.

I faced with this problem when I had to programmatically send an email to our customers. I used SMTP protocol, where every space counts, so every single line command should not have leading spaces. And of course we want to have a method without splitting source text by line.

So, the idea is to remove preceding spaces and tabs. That’s where regular expression help us.

The algorithm is following: if line of text starts with one or more spaces or tabs just remove them.

Regular expression FTW

Here I’ll briefly introduce you to regular expressions. RegExps are one of the most powerful tools every programmer should know. Oh, not only programmers, but system administrators and engineers also should know them. Every regular expression consists of flags and ordinary symbols, so the idea here is to learn what every flag mean. In our example the final regular expression looks like /^( |\t)+/, let’s figure out what every part of it is :

  1. /.../ - two backslashes shows us that everything between them is a regular expression, sometimes this slash delimeter could differ, for example a hash symbol # or something else.
  2. ^ is start line flag, so every further symbol after ^ shows as that line should start from this symbol, for example ^A matches to line starting from A.
  3. (a|b|c|d) - this construction matches to character that could be a or b or c or d. But it could be only one character. It also capturing the final result, as a result we could know what symbol exactly have been matched. In our example ' ' is space character and \t is tab character.
  4. + matches to one or more previous character.

Reading the whole regexp /^( |\t)+/ will look something like this - match every single line, which starts from one or more tab or space.

Woot! That’s what we need. Now, using gsub in ruby we can replace all matched substrings in text with no text

text.gsub /^( |\t)+/, ""

Applying this filtering exactly in place where we want to use it, we came to complete listing.

Complete listing

require 'securerandom'
def message
    marker = SecureRandom.hex(15);
    <<-EOF.gsub /^( |\t)+/, ""
	    From: from <from@email.com>
	    To: subscriber <to@email.com>
	    Subject: Subject
	    Message-Id: <#{SecureRandom.uuid}@example.com>
	    MIME-Version: 1.0
	    Content-Type: multipart/mixed; boundary=#{marker}
	    --#{marker}
	    Content-Type: text/plain
	    Content-Transfer-Encoding:8bit
	    Hello wrld!
	    --#{marker}
    EOF
end
def message2
    "select attr1, attr2, attr3, attr4, attr5, attr6, attr7
    from table1, table2, table3, etc, etc, etc, etc, etc,
    where etc etc etc etc etc etc etc etc etc etc etc etc etc".gsub /^( |\t)+/, ""
end
puts message
puts "=================================="
puts message2

and result

From: from <from@email.com>
To: subscriber <to@email.com>
Subject: Subject
Message-Id: <7eb27faf-afa2-4392-a88c-0e7229dcefdb@example.com>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=809c7259766834dd5442926e7dad98
--809c7259766834dd5442926e7dad98
Content-Type: text/plain
Content-Transfer-Encoding:8bit
Hello wrld!
--809c7259766834dd5442926e7dad98
==================================
select attr1, attr2, attr3, attr4, attr5, attr6, attr7
from table1, table2, table3, etc, etc, etc, etc, etc,
where etc etc etc etc etc etc etc etc etc etc etc etc etc