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

397

¿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'

Como, ¿hay alguna manera de implicar concatenación?

Zombis
fuente
28
Tenga cuidado con los ataques de inyección SQL. :)
Roy Tinker

Respuestas:

595

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 la sintaxis divertida de HEREDOC (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"

Este último sería principalmente para situaciones que requieren más flexibilidad en el procesamiento. Personalmente no me gusta, coloca el procesamiento en un lugar extraño con la cadena (es decir, delante de él, 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, en p <<-END_SQLlugar 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 para mí se ve mejor que usar signos +.

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

EDITAR: Agregar 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
fuente
2
Esta es una vieja pregunta PERO hay un error en la respuesta o ha habido un cambio en la sintaxis desde entonces. p <<END_SQLdebe ser p <<-END_SQLlo demás, es la respuesta. opcionalmente puede eliminar espacios en blanco con el operador HEREDOC ondulado,<<~END_SQL
jaydel
Es solo un error si el identificador final está sangrado (el guión le dice al intérprete ruby ​​que recorte los espacios en blanco antes de hacer la determinación del identificador final). Sin embargo, puedo poner una nota mencionando eso. Además, el ~ es innecesario, gsub \ s + y strip ya están eliminando los espacios en blanco iniciales.
A. Wilson
Agregar <<~a la respuesta sería bueno, terminé investigando eso desde allí. Personalmente, uso el <<~MSG.strip ... MSGque también elimina el último \n.
Qortex
1
Cuando escribí esta respuesta (¡hace nueve años, sheesh!), Ruby estaba en 1.9, y << ~ (evidentemente) no se presentó hasta 2.3. De todos modos, dejando de lado la historia antigua, lo pondré, gracias por mencionarlo.
A. Wilson
Gracias por ser una de las pocas respuestas que no agrega nuevas líneas adicionales, que es lo que estaba tratando de evitar cuando encontré esta pregunta.
Josh
174

En ruby ​​2.0 ahora puedes usar %

Por ejemplo:

SQL = %{
SELECT user, name
FROM users
WHERE users.id = #{var}
LIMIT #{var2}
}
Robbie Guilfoyle
fuente
14
Funciona en Ruby 1.9.3 también.
Andy Stewart
26
Una cadena creada con esta sintaxis incluirá tanto nuevas líneas como cualquier sangría agregada a las líneas siguientes.
James
¡Esto es incluso mejor que << EOT ...... EOT (aquí documento)! También hace interpolación si es necesario.
Nasser
1
@Nasser A heredoc también hace interpolación.
Financia la demanda de Mónica el
3
Si usar Rails invocar squishen la salida debería ser útil.
Jignesh Gohel
167

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, puede 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
fuente
87
También podría usar%Q(...)
BaroqueBobcat
3
@ Zombies: las líneas nuevas generalmente se permiten en las instrucciones SQL y solo se tratan como espacios en blanco comunes.
Mark Byers
2
vea mi respuesta a continuación para ver un ejemplo, puede usar% ahora.
Robbie Guilfoyle
44
También podría usar%(...)
divisor cero
1
Algo importante a tener en cuenta si agrega espacio en blanco al final y utiliza una de estas soluciones es que su editor puede eliminar automáticamente el espacio al guardar el archivo. Aunque normalmente prefiero este comportamiento, me ha causado problemas inesperados varias veces. Una solución es escribir su cadena de varias líneas como lo hizo el OP en la pregunta.
Dennis
50

Hay varias sintaxis para cadenas de varias líneas, como ya ha leído. Mi favorito es el estilo Perl:

conn.exec %q{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}

La cadena de varias líneas comienza con% q, seguida de un {, [o (, y luego termina con el carácter inverso correspondiente.% Q no permite la interpolación;% Q lo hace para que pueda escribir cosas como esta:

conn.exec %Q{select attr1, attr2, attr3, attr4, attr5, attr6, attr7
      from #{table_names},
      where etc etc etc etc etc etc etc etc etc etc etc etc etc}

De hecho, no tengo idea de cómo se llaman este tipo de cadenas de varias líneas, así que llamémoslas Perl multilíneas.

Sin embargo, tenga en cuenta que si usa líneas múltiples Perl o heredocs como Mark y Peter han sugerido, terminará con espacios en blanco potencialmente innecesarios. Tanto en mis ejemplos como en sus ejemplos, las líneas "desde" y "dónde" contienen espacios en blanco iniciales debido a su sangría en el código. Si no se desea este espacio en blanco, debe usar cadenas concatenadas como lo está haciendo ahora.

Hongli
fuente
44
from # {table_names} no funcionaría en este ejemplo, ya que usó% q {}, funcionaría si usó% q [] o ()
MatthewFord
2
Mi favorito en este sentido es solo% {cadena súper multilínea con soporte de interpolación}
Duke
Las cadenas producidas a partir de la %qfamilia incluirán las nuevas líneas que no son equivalentes al código original.
Josh
29

A veces vale la pena eliminar nuevos caracteres de línea \ncomo:

conn.exec <<-eos.squish
 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
Kamil Lelonek
fuente
55
esto se basa en rieles, no en rubí
a14m
23

También puedes usar comillas dobles

x = """
this is 
a multiline
string
"""

2.3.3 :012 > x
 => "\nthis is\na multiline\nstring\n"

Si es necesario para eliminar saltos de línea "\ n", utilice la barra invertida "\" al final de cada línea

juliangonzalez
fuente
55
Puede lograr el mismo resultado con las comillas dobles singulares. No hay tal cosa como triples comillas dobles en Ruby. Simplemente los interpreta como "" + "double quotes with some content" + "".
rakvium
Sí, pero `" "+" \ n hola \ n "+" "Se ve raro
juliangonzalez
1
Sí, parece extraño, y esta es la razón por la cual no hay razón para agregar comillas dobles adicionales cuando solo puede usar comillas dobles singulares con el mismo resultado.
rakvium
Sí, quise decir el signo más. Las comillas dobles sin ella se ven bien, es legible y más fácil de detectar en lugar de una comilla simple, que debe usarse en cadenas de una sola línea.
juliangonzalez
1
Quiero decir que solo se "x"ve mejor y funciona más rápido que """x"""(que es básicamente lo mismo que ""+"x"+"") o """""x"""""(que es lo mismo que "" + "" + "x" + "" + ""). Es Ruby, no Python, donde se usa en """lugar de "cuando se necesita una cadena de varias líneas.
rakvium
15
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
Peter
fuente
1
el uso de heredoc sin el '-', como en '<< - eos', incluirá los espacios de líder adicionales. ver la respuesta de Mark Byers.
ives
heredoc incluirá las nuevas líneas que no es equivalente al código original.
Josh
15

Otras opciones:

#multi line string
multiline_string = <<EOM
This is a very long string
that contains interpolation
like #{4 + 5} \n\n
EOM

puts multiline_string

#another option for multiline string
message = <<-EOF
asdfasdfsador #{2+2} this month.
asdfadsfasdfadsfad.
EOF

puts message
Alex Cohen
fuente
1
Debería cambiar <<EOMa <<-EOM, ¿no?
kingPuppy
Tal vez, parecía funcionar para mi <<-EOFejemplo. Mi suposición es que de cualquier manera funciona.
Alex Cohen
heredoc incluirá las nuevas líneas que no es equivalente al código original.
Josh
11

Recientemente, con las nuevas características en Ruby 2.3, la nueva squiggly HEREDOCle permitirá escribir nuestras cadenas multilínea de una manera agradable con un cambio mínimo, por lo que usar esto combinado con .squish(si está usando rieles) le permitirá escribir multilínea de una manera agradable. en caso de usar solo ruby, puedes hacer uno <<~SQL.split.join(" ")que sea casi igual

[1] pry(main)> <<~SQL.squish
[1] pry(main)*   select attr1, attr2, attr3, attr4, attr5, attr6, attr7
[1] pry(main)*   from table1, table2, table3, etc, etc, etc, etc, etc,
[1] pry(main)*   where etc etc etc etc etc etc etc etc etc etc etc etc etc
[1] pry(main)* SQL
=> "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"

ref: https://infinum.co/the-capsized-eight/multiline-strings-ruby-2-3-0-the-squiggly-heredoc

Mark Jad
fuente
squish es rieles, no rubí
Josh
1
@ Josh, sí, tienes razón, actualizó la respuesta, saludos.
Mark Jad
6
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'

<< es el operador de concatenación para cadenas

Dom Brezinski
fuente
2
+es el operador de concatenación regular, <<es el operador de adición in situ . El uso de efectos secundarios en un literal funciona aquí (la primera cadena se modifica dos veces y se devuelve), en mi humilde opinión, es extraño y me hace hacer una doble toma, donde +estaría perfectamente claro. Pero tal vez soy nuevo en Ruby ...
Beni Cherniavsky-Paskin
Esto no funcionará si frozen_string_literalestá habilitado
Raido
6

Si haces espacios y nuevas líneas adicionales mente, puede utilizar

conn.exec %w{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} * ' '

(use% W para cadenas interpoladas)

Tío Gene
fuente
Este me gusta mucho porque permite muchas más combinaciones de uso.
schmijos
1
Esto aplastará múltiples espacios adyacentes en uno. (Su aplastamiento de nueva línea + siguiente sangría es una victoria aquí, pero en el medio de la línea puede ser sorprendente.)
Beni Cherniavsky-Paskin
5

Para evitar cerrar los paréntesis para cada línea, simplemente puede usar comillas dobles con una barra invertida para escapar de la nueva línea:

"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"
Pwnrar
fuente
¡Esta es una de las pocas respuestas en esta página que realmente responde la pregunta!
Josh
4
conn.exec [
  "select attr1, attr2, attr3, ...",
  "from table1, table2, table3, ...",
  "where ..."
].join(' ')

Esta sugerencia tiene la ventaja sobre los documentos aquí y las cadenas largas que los auto-indentadores pueden sangrar cada parte de la cadena de manera apropiada. Pero tiene un costo de eficiencia.

Aidan Cully
fuente
@Aidan, puede reemplazar las comas con barras invertidas (a la C) y no se necesitará una unión (o matriz): el intérprete concatenará las cadenas en el tiempo de análisis (creo), lo que lo hace bastante rápido en comparación con la mayoría de las alternativas . Sin embargo, una ventaja de unir una serie de cadenas es que algunos auto-indentadores hacen un trabajo mejor que con, por ejemplo, cadenas de documentos aquí o con \.
Wayne Conrad
1
Una nota, la sintaxis heredoc << - permitirá la sangría apropiada.
A. Wilson
3

Ruby-way (TM) desde Ruby 2.3: use el HEREDOC ondulado <<~ para definir una cadena de varias líneas con líneas nuevas y sangría adecuada:

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, 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 eso requeriría mucho escape, 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 causan tanto el HEREDOC ondulado, las comillas como el literal de cadena de porcentaje), entonces se puede usar una continuación de línea colocando una barra diagonal inversa \como el último carácter no en blanco en una línea. Esto continuará la línea y hará que Ruby concatene las cadenas de forma consecutiva (tenga cuidado con esos 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.squish, eliminará la cadena de espacios iniciales y finales y colapsará todos los espacios en blanco consecutivos (líneas nuevas, pestañas 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 documento aquí para cadenas funciona es una forma de designar bloques largos de texto en línea en el código. Se inicia <<seguido de una Cadena definida por el usuario (el finalizador de Fin de Cadena). Todas las siguientes líneas se concatenan hasta que se encuentra el finalizador de Fin de cadena al comienzo 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 cadena como "SQL".

HEREDOC admite la interpolación de manera predeterminada o cuando el terminador EOS se cita dos veces:

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 deshabilitar si el terminador EOS se cita solo:

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

Una restricción importante de la <<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 evitar 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 el EOS todavía se usan en toda su extensión, incluida toda sangría:

puts <<-EOS # Use <<- to indent End of String terminator
  def foo
    print "foo"
  end
EOS
# -> "..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"

<< ~ ignora las líneas vacías y las líneas que solo contienen pestañas y espacios.

puts <<~EOS.inspect 
  Hello

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

Si se usan tabulaciones y espacios, las pestañas se consideran iguales a 8 espacios. Si la línea con menos 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 locuras, como ejecutar comandos usando backticks:

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 e iniciará 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 en <<EOSrealidad es solo una cadena literal y se puede poner donde sea que se pueda poner 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 tiene Ruby 2.3, pero Rails >=3.0, puede usar el String.strip_heredocque 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

Porcentaje de cadenas literales

Ver RubyDoc para saber cómo utilizar el signo de porcentaje seguido de una cadena en un par de paréntesis, tales como %(...), %[...],%{...} , etc., o un par de cualquier carácter no alfanumérico, como%+...+

Ultimas palabras

Por último, para obtener la respuesta a la pregunta original "¿Hay alguna forma de implicar concatenación?" respondió: Ruby siempre implica concatenación si se encuentran dos cadenas (comillas simples y dobles) consecutivas:

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 la declaración y la línea consecuente de solo cadenas en una línea no hace nada.

Christopher Oezbek
fuente
1

Respuesta elegante hoy:

<<~TEXT
Hi #{user.name}, 

Thanks for raising the flag, we're always happy to help you.
Your issue will be resolved within 2 hours.
Please be patient!

Thanks again,
Team #{user.organization.name}
TEXT

Hay una diferencia en <<-TEXTy<<~TEXT , el primero conserva el espacio dentro del bloque y el segundo no.

Hay otras opciones tambien. Como la concatenación, etc., pero esta tiene más sentido en general.

Si me equivoco aquí, avíseme cómo ...

Sandip Mane
fuente
heredoc incluirá las nuevas líneas que no es equivalente al código original.
Josh
1

Al igual que usted, también estaba buscando una solución que no incluye nuevas líneas . (Si bien pueden ser seguros en SQL, no lo son en mi caso y tengo un gran bloque de texto para tratar)

Esto podría decirse que es igual de feo, pero puede hacer una barra invertida para escapar de las nuevas líneas en un heredoc para omitirlas de la cadena resultante:

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

Tenga en cuenta que no puede hacerlo sin interpolación (IE <<~'END_OF_INPUT'), así que tenga cuidado. #{expressions}serán evaluados aquí, mientras que no lo harán en su código original. La respuesta de A. Wilson puede ser mejor por esa razón.

Josh
fuente