Song search app with dynamic tables
I'm going to assume that search returns a data structure like this:

data = [
  { 'ArtistName' =&gt; 'Crash Test Dummies',
    'AlbumName' =&gt; 'God Shuffled His Feet',
    'SongName' =&gt; 'Mmmm_Mmmmm...',
  },
  { 'ArtistName' =&gt; 'John Powell',
    'AlbumName' =&gt; 'How to Train Your Dragon',
    'SongName' =&gt; 'Test Drive',
  },
  …
]


In that case, using song_hash as the name of the parameter to view_song_search is misleading, since it's actually an array.

There's no reason why view_song_search needs to know anything about songs, or why it should be tied to artists, albums, and songs at all.  You should write a generic table-formatting function, like this:

puts table(data, [['ArtistName', 'Artist'],
                  ['AlbumName', 'Album'],
                  ['SongName', 'Song']])


Here's an implementation of such a function.  Remarks:


A large chunk of the code is the block in the middle to shrink overwide tables.  I've tried to scale each column proportionally instead of giving one-third to each column.
The heading row resembles a data row in many ways, and should be handled using a common codepath.
The horizontal rules are printed many times; build it just once.
Use sprintf '%-*s', width, text to space-pad the text to the desired width.




def table(hashes, headings, width_limit=%x'tput cols'.to_i - 5)
  attrs = headings.map { |h| h.first }
  headings = Hash[headings]

  # Minimum widths to accommodate the heading and data of each column,
  # excluding leading space, trailing space, and vertical dividers
  col_widths = attrs.map do |attr|
    [headings[attr].size, hashes.map { |h| h[attr].size }.max].max
  end

  # If the desired width, including delimiters, exceeds the limit,
  # reduce column widths proportionally
  desired_width = col_widths.inject(:+) + 3 * col_widths.size + 1
  if desired_width &gt; width_limit
    col_widths.each_with_index do |width, c|
      col_widths[c] = (width.to_f * width_limit / desired_width).to_i - 2
    end

    # Readjust last column for round-off errors
    desired_width = col_widths.inject(:+) + 3 * col_widths.size + 1
    col_widths[-1] += width_limit - desired_width
  end

  output = hrule = '+' + col_widths.map { |width| '-' * (width + 2) + '+' }.join
  ([headings] + hashes).each_with_index do |row, r|
    output += "\n"
    output += "\e[7m" if r == 0         # Highlight heading row
    output += '|'
    attrs.each_with_index do |attr, c|
      output += sprintf(' %-*s |', col_widths[c], row[attr][0...col_widths[c]])
    end
    output += "\e[0m" if r == 0         # Turn off highlight
    output += "\n" + hrule
  end
  output
end