Ruby: ¿Puedo escribir cadenas de varias líneas sin concatenación?

Resuelto Zombies asked hace 14 años • 16 respuestas

¿Hay alguna manera de hacer que esto se vea un poco mejor?

conn.exec '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'

¿Hay alguna manera de implicar concatenación?

Zombies avatar Feb 26 '10 03:02 Zombies
Aceptado

Hay partes de esta respuesta que me ayudaron a obtener lo que necesitaba (concatenación fácil de varias líneas SIN espacios en blanco adicionales), pero como ninguna de las respuestas reales lo tenía, las estoy compilando aquí:

str = 'this is a multi-line string'\
  ' using implicit concatenation'\
  ' to prevent spare \n\'s'

=> "this is a multi-line string using implicit concatenation to eliminate spare
\\n's"

Como beneficio adicional, aquí hay una versión que usa una sintaxis HEREDOC divertida (a través de este enlace ):

p <<END_SQL.gsub(/\s+/, " ").strip
SELECT * FROM     users
         ORDER BY users.id DESC
END_SQL
# >> "SELECT * FROM users ORDER BY users.id DESC"

Esto último sería principalmente para situaciones que requirieran más flexibilidad en el procesamiento. A mí personalmente no me gusta, coloca el procesamiento en un lugar extraño con respecto a la cadena (es decir, delante de ella, pero usando métodos de instancia que generalmente vienen después), pero está ahí. Tenga en cuenta que si está sangrando el último END_SQLidentificador (lo cual es común, ya que probablemente esté dentro de una función o módulo), deberá usar la sintaxis con guiones (es decir, p <<-END_SQLen lugar de p <<END_SQL). De lo contrario, el espacio en blanco de sangría hace que el identificador se interprete como una continuación de la cadena.

Esto no ahorra mucho tipeo, pero a mí me parece mejor que usar los signos +.

Además (lo digo en una edición, varios años después), si estás usando Ruby 2.3+, el operador <<~ también está disponible , lo que elimina la sangría adicional de la cadena final. En ese caso, debería poder eliminar la .gsubinvocación (aunque podría depender tanto de la sangría inicial como de sus necesidades finales).

EDITAR: Añadiendo uno más:

p %{
SELECT * FROM     users
         ORDER BY users.id DESC
}.gsub(/\s+/, " ").strip
# >> "SELECT * FROM users ORDER BY users.id DESC"
A. Wilson avatar May 17 '2011 15:05 A. Wilson

En Ruby 2.0 ahora puedes usar%

Por ejemplo:

    SQL = %{
      SELECT user, name
      FROM users
      WHERE users.id = #{var}
      LIMIT #{var2}
    }
Robbie Guilfoyle avatar Aug 28 '2013 01:08 Robbie Guilfoyle

Sí, si no le importa que se inserten nuevas líneas adicionales:

 conn.exec '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'

Alternativamente, puedes usar un heredoc :

conn.exec <<-eos
   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
eos
Mark Byers avatar Feb 25 '2010 20:02 Mark Byers

Esta pregunta me hizo enloquecer para entender cómo funciona HEREDOC. Disculpe si la respuesta fue demasiado larga.

El ondulado HEREDOC <<~ es lo que busca cuando desea definir una cadena de varias líneas con nuevas líneas y sangría adecuada (disponible desde Ruby 2.3):

conn.exec <<~EOS
            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
          EOS

# -> "select...\nfrom...\nwhere..."

Si la sangría adecuada no es una preocupación, entonces las comillas simples y dobles pueden abarcar varias líneas en Ruby:

conn.exec "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"    

# -> "select...\n           from...\n           where..."
      

Si las comillas simples o dobles son engorrosas porque requerirían muchos escapes, entonces la notación literal de cadena de porcentaje % es la solución más flexible:

conn.exec %(select attr1, attr2, attr3, attr4, attr5, attr6, attr7
            from table1, table2, table3, etc, etc, etc, etc, etc
            where (ProductLine = 'R' OR ProductLine = "S") AND Country = "...")
# -> "select...\n            from...\n            where..."

Si el objetivo es evitar las nuevas líneas (que causarán tanto el garabato HEREDOC, las comillas y el literal de cadena de porcentaje), entonces se puede usar una continuación de línea poniendo una barra invertida \como último carácter que no sea un espacio en blanco en una línea. Esto continuará la línea y hará que Ruby concatene las cadenas una detrás de otra (tenga cuidado con los espacios dentro de la cadena citada):

conn.exec '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'

# -> "select...from...where..."

Si usa Rails, String.squisheliminará la cadena de espacios iniciales y finales y colapsará todos los espacios en blanco consecutivos (nuevas líneas, tabulaciones y todo) en un solo espacio:

conn.exec "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".squish

# -> "select...attr7 from...etc, where..."

Más detalles:

Sintaxis de Ruby HEREDOC

La notación de documentos Here para cadenas es una forma de designar largos bloques de texto en línea en el código. Se inicia <<seguido de una cadena definida por el usuario (el terminador de fin de cadena). Todas las líneas siguientes se concatenan hasta que se encuentra el terminador de fin de cadena al principio de una línea:

puts <<HEREDOC 
Text Text Text Text
Bla Bla
HEREDOC
# -> "Text Text Text Text\nBlaBla"

El terminador End of String se puede elegir libremente, pero es común usar algo como "EOS" (End of String) o algo que coincida con el dominio de la String como "SQL".

HEREDOC admite la interpolación de forma predeterminada o cuando el terminador EOS está entre comillas dobles:

price = 10
print <<"EOS"  # comments can be put here
1.) The price is #{price}.
EOS
# -> "1.) The price is 10."

La interpolación se puede desactivar si el terminador EOS está entre comillas simples:

print <<'EOS' # Disabled interpolation
3.) The price is #{price}.
EOS
# -> "3.) The price is #{price}."

Una restricción importante <<HEREDOCes que el terminador de fin de cadena debe estar al principio de la línea:

  puts <<EOS 
    def foo
      print "foo"
    end
  EOS
EOS
#-> "....def foo\n......print "foo"\n....end\n..EOS"

Para solucionar esto, <<-se creó la sintaxis. Permite sangrar el terminador EOS para que el código se vea mejor. Las líneas entre el <<-terminador y EOS todavía se utilizan en toda su extensión, incluida toda la sangría:

def printExample
  puts <<-EOS # Use <<- to indent End of String terminator
    def foo
      print "foo"
    end
  EOS
end
# -> "....def foo\n......print "foo"\n....end"

Desde Ruby 2.3, ahora tenemos el HEREDOC ondulado <<~que elimina los espacios en blanco iniciales:

puts <<~EOS # Use the squiggly HEREDOC <<~ to remove leading whitespace (since Ruby 2.3!)
  def foo
    print "foo"
  end
EOS
# -> "def foo\n..print "foo"\nend"

Las líneas vacías y las líneas que solo contienen tabulaciones y espacios son ignoradas por <<~

puts <<~EOS.inspect 
  Hello

    World!
EOS
#-> "Hello\n..World!"

Si se utilizan tabulaciones y espacios, las tabulaciones se consideran iguales a 8 espacios. Si la línea con menor sangría está en el medio de una pestaña, esta pestaña no se elimina.

puts <<~EOS.inspect
<tab>One Tab
<space><space>Two Spaces
EOS
# -> "\tOne Tab\nTwoSpaces"

HEREDOC puede hacer algunas cosas locas, como ejecutar comandos usando comillas invertidas:

puts <<`EOC`            
echo #{price}
echo #{price * 2}
EOC

Las definiciones de cadenas HEREDOC se pueden "apilar", lo que significa que el primer terminador EOS (EOSFOO a continuación) finalizará la primera cadena y comenzará la segunda (EOSBAR a continuación):

print <<EOSFOO, <<EOSBAR    # you can stack them
I said foo.
EOSFOO
I said bar.
EOSBAR

No creo que nadie lo use como tal, pero <<EOSen realidad es solo un literal de cadena y se puede colocar donde normalmente se puede colocar una cadena:

def func(a,b,c)
  puts a
  puts b
  puts c
end

func(<<THIS, 23, <<THAT) 
Here's a line
or two.
THIS
and here's another.
THAT

Si no tienes Ruby 2.3, pero sí Rails >=3.0, entonces puedes usar String.strip_heredocel cual hace lo mismo que<<~

# File activesupport/lib/active_support/core_ext/string/strip.rb, line 22
class String
  def strip_heredoc
    gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze)
  end
end

puts <<-USAGE.strip_heredoc # If no Ruby 2.3, but Rails >= 3.0
  This command does such and such.

  Supported options are:
    -h         This message
    ...
USAGE

Solución de problemas

Si ve errores cuando Ruby analiza su archivo, lo más probable es que tenga espacios iniciales o finales adicionales con un HEREDOC o espacios finales adicionales con un HEREDOC ondulado. Por ejemplo:

Lo que ves:

    database_yml = <<~EOS
      production:
        database: #{fetch(:user)}
        adapter: postgresql
        pool: 5
        timeout: 5000
    EOS  

Lo que Ruby te dice:

SyntaxError: .../sample.rb:xx: can't find string "EOS" anywhere before EOF
...sample.rb:xx: syntax error, unexpected end-of-input, expecting `end'

Qué tiene la culpa:

Detecte los espacios adicionales después del EOS final

Localice los espacios adicionales después del EOS final.

Porcentaje de literales de cadena

Consulte RubyDoc para saber cómo utilizar el signo de porcentaje seguido de una cadena entre paréntesis como %(...), %[...], %{...}etc. o un par de caracteres no alfanuméricos como%+...+

Ultimas palabras

Por último, para obtener la respuesta a la pregunta original "¿Existe alguna forma de implicar concatenación?" Respondido: Ruby siempre implica concatenación si se encuentran dos cadenas (comillas simples y dobles) una detrás de otra:

puts "select..." 'from table...' "where..."
# -> "select...from table...where..."

La advertencia es que esto no funciona a través de saltos de línea, porque Ruby está interpretando el final de una declaración y la línea consiguiente de solo cadenas en una línea no hace nada.

Christopher Oezbek avatar Mar 19 '2020 21:03 Christopher Oezbek