Ruby: posso scrivere una stringa multilinea senza concatenazione?


397

C'è un modo per rendere questo aspetto un po 'migliore?

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'

Ad esempio, c'è un modo per implicare la concatenazione?


28
Prestare attenzione agli attacchi di iniezione SQL. :)
Roy Tinker,

Risposte:


595

Ci sono pezzi di questa risposta che mi hanno aiutato a ottenere ciò di cui avevo bisogno (facile concatenazione su più righe SENZA spazi bianchi extra), ma poiché nessuna delle risposte effettive lo aveva, li sto compilando qui:

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"

Come bonus, ecco una versione che usa la sintassi HEREDOC divertente (tramite questo link ):

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

Quest'ultimo sarebbe principalmente per situazioni che richiedevano maggiore flessibilità nell'elaborazione. Personalmente non mi piace, mette l'elaborazione in un posto strano rispetto alla stringa (cioè, davanti ad essa, ma usando metodi di istanza che di solito vengono dopo), ma è lì. Nota che se stai indentando l'ultimo END_SQLidentificatore (che è comune, poiché questo è probabilmente all'interno di una funzione o di un modulo), dovrai usare la sintassi sillabata (cioè, p <<-END_SQLinvece di p <<END_SQL). Altrimenti, lo spazio bianco di rientro fa sì che l'identificatore venga interpretato come una continuazione della stringa.

Questo non risparmia molto nella digitazione, ma sembra più bello dell'uso dei segni +, per me.

Inoltre (dico in una modifica, diversi anni dopo), se si utilizza Ruby 2.3+, è disponibile anche l'operatore << ~ , che rimuove il rientro aggiuntivo dalla stringa finale. Dovresti essere in grado di rimuovere l' .gsubinvocazione, in quel caso (anche se potrebbe dipendere sia dal rientro iniziale che dalle tue esigenze finali).

EDIT: aggiungendo un altro:

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

2
Questa è una vecchia domanda MA c'è un errore nella risposta o da allora c'è stato un cambiamento nella sintassi. p <<END_SQLdovrebbe essere p <<-END_SQLAltrimenti questa è la risposta. opzionalmente è possibile togliere gli spazi restanti con l'operatore squiggly heredoc,<<~END_SQL
jaydel

È solo un errore se l'identificatore finale è indentato (il trattino dice all'interprete rubino di tagliare gli spazi bianchi prima di effettuare la determinazione dell'identificatore finale). Posso mettere una nota che menziona questo, però. Inoltre, ~ non è necessario, gsub \ s + e strip stanno già rimuovendo i primi spazi bianchi.
A. Wilson,

Aggiungere <<~alla risposta sarebbe bello, finendo per ricercarlo da lì. Personalmente, io uso <<~MSG.strip ... MSGche spoglia anche l'ultimo \n.
Qortex,

1
Quando ho scritto questa risposta (nove anni fa, sheesh!), Ruby era in 1.9 e << ~ (evidentemente) non è stato introdotto fino alla 2.3. Comunque, a parte la storia antica, la inserirò, grazie per averla citata.
A. Wilson,

Grazie per essere una delle poche risposte che non aggiunge nuove righe, ed è quello che stavo cercando di evitare quando ho trovato questa domanda.
Josh

174

In ruby ​​2.0 ora puoi semplicemente usare %

Per esempio:

SQL = %{
SELECT user, name
FROM users
WHERE users.id = #{var}
LIMIT #{var2}
}

14
Funziona anche in Ruby 1.9.3.
Andy Stewart,

26
Una stringa creata con questa sintassi includerà sia le nuove righe che eventuali rientri aggiunti alle righe successive.
James,

Questo è persino meglio di << EOT ...... EOT (qui documento)! fa anche interpolazione se necessario.
Nasser,

1
@Nasser Anche un ereditario fa interpolazione.
Fund Monica's Lawsuit

3
Se si utilizza Rails invocare squishl'output dovrebbe essere utile.
Jignesh Gohel,

167

Sì, se non ti dispiace inserire le nuove righe extra:

 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'

In alternativa puoi usare 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

87
È inoltre possibile utilizzare%Q(...)
BaroqueBobcat il

3
@Zombies: le nuove righe sono generalmente consentite nelle istruzioni SQL e sono trattate come normali spazi bianchi.
Mark Byers,

2
vedi la mia risposta qui sotto per un esempio, puoi semplicemente usare% ora.
Robbie Guilfoyle,

4
Potresti anche usare%(...)
zero-divisor

1
Qualcosa di importante da tenere a mente se si aggiunge intenzionalmente spazi vuoti finali e si utilizza una di queste soluzioni è che l'editor può rimuovere automaticamente lo spazio finale quando si salva il file. Mentre di solito preferisco questo comportamento, ha causato problemi imprevisti per me alcune volte. Una soluzione è scrivere la tua stringa multilinea come l'OP nella domanda.
Dennis,

50

Esistono più sintassi per le stringhe multilinea come hai già letto. Il mio preferito è in stile 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 stringa multilinea inizia con% q, seguita da un {, [o (, e quindi terminata dal corrispondente carattere invertito.% Q non consente l'interpolazione;% Q lo fa in modo da poter scrivere cose come questa:

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}

In realtà non ho idea di come vengano chiamati questi tipi di stringhe multilinea, quindi chiamiamole multiline Perl.

Nota comunque che se usi multiline Perl o heredocs come suggerito da Mark e Peter, finirai con spazi bianchi potenzialmente non necessari. Sia nei miei esempi che nei loro esempi, le righe "from" e "where" contengono spazi bianchi iniziali a causa della loro indentazione nel codice. Se questo spazio bianco non è desiderato, è necessario utilizzare stringhe concatenate come si sta facendo ora.


4
da # {table_names} non funzionerebbe in questo esempio, poiché hai usato% q {}, funzionerebbe se avessi usato% q [] o ()
MatthewFord

2
Il mio preferito in questo senso è solo% {stringa super multilinea con supporto interpolazione}
Duke,

le stringhe prodotte dalla %qfamiglia includeranno le nuove righe che non sono equivalenti al codice originale.
Josh

29

A volte vale la pena rimuovere i nuovi caratteri di linea \ncome:

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

5
questo è rotaie non rubino
a14m

23

Puoi anche usare virgolette doppie

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

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

Se necessario per rimuovere le interruzioni di riga "\ n" utilizzare la barra rovesciata "\" alla fine di ogni riga


5
Puoi ottenere lo stesso risultato con le doppie virgolette singole. Non esistono cose come le doppie virgolette doppie in Ruby. Li interpreta semplicemente come "" + "double quotes with some content" + "".
rakvium

Sì, ma `" "+" \ n ciao \ n "+" "
Sembra

1
Sì, sembra strano, ed è per questo che non c'è motivo di aggiungere virgolette doppie extra quando puoi semplicemente usare le doppie virgolette singole con lo stesso risultato.
rakvium,

Sì, intendevo il segno più. Le virgolette doppie senza di essa sembrano perfette, è leggibile e più facile da individuare invece di una virgoletta singola, che dovrebbe essere utilizzata su stringhe a riga singola.
juliangonzalez,

1
Voglio dire che "x"sembra migliore e funziona più velocemente di """x"""(che è sostanzialmente lo stesso di ""+"x"+"") o """""x"""""(che è lo stesso di "" + "" + "x" + "" + ""). È Ruby, non Python, dove usi """invece di "quando hai bisogno di una stringa multilinea.
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

1
l'uso di heredoc senza '-', come in '<< - eos', includerà gli spazi guida aggiuntivi. vedere la risposta di Mark Byers.
vive il

heredoc includerà le nuove righe che non equivalgono al codice originale.
Josh

15

Altre opzioni:

#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

1
Dovrebbe cambiare <<EOMin <<-EOM, no?
KingPuppy,

Forse, sembrava funzionare per il mio <<-EOFesempio. La mia ipotesi è che funziona in entrambi i modi.
Alex Cohen,

heredoc includerà le nuove righe che non equivalgono al codice originale.
Josh

11

Di recente con le nuove funzionalità di Ruby 2.3 il nuovo squiggly HEREDOCti permetterà di scrivere le nostre stringhe multilinea in modo piacevole con una modifica minima, quindi l'utilizzo di questo in combinazione con .squish(se stai usando le rotaie) ti permetterà di scrivere multilinea in modo piacevole! nel caso in cui usi solo il rubino, puoi fare uno <<~SQL.split.join(" ")che è quasi lo stesso

[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"

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


squish è rotaie, non rubino
Josh

1
@Josh, sì hai ragione, aggiornata la risposta, evviva.
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'

<< è l'operatore di concatenazione delle stringhe


2
+è l'operatore di concatenazione regolare, <<è l' operatore append sul posto . L'uso di effetti collaterali su un valore letterale sembra funzionare qui (la prima stringa viene modificata due volte e restituita) ma IMHO è strano e mi fa fare un doppio take, dove +sarebbe perfettamente chiaro. Ma forse sono solo una novità di Ruby ...
Beni Cherniavsky-Paskin,

Questo non funzionerà se frozen_string_literalè abilitato
Raido,

6

Se fai mente spazi aggiuntivi e ritorni a capo, è possibile utilizzare

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} * ' '

(usa% W per stringhe interpolate)


Mi piace molto perché consente molte più combinazioni di utilizzo.
schmijos,

1
Questo schiaccerà più spazi adiacenti in uno. (La sua schiacciata di newline + dopo il rientro è una vittoria qui, ma a metà linea potrebbe essere sorprendente.)
Beni Cherniavsky-Paskin,

5

Per evitare di chiudere le parentesi per ogni riga, puoi semplicemente usare le virgolette doppie con una barra rovesciata per sfuggire alla nuova riga:

"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"

Questa è una delle poche risposte in questa pagina che risponde effettivamente alla domanda!
Josh

4
conn.exec [
  "select attr1, attr2, attr3, ...",
  "from table1, table2, table3, ...",
  "where ..."
].join(' ')

Questo suggerimento ha il vantaggio rispetto ai documenti e alle stringhe lunghe che i rientri automatici possono indentare in modo appropriato ciascuna parte della stringa. Ma ha un costo in termini di efficienza.


@Aidan, puoi sostituire le virgole con le barre rovesciate (alla C) e non sarà necessario alcun join (o array): l'interprete concatenerà le stringhe al momento dell'analisi (penso), rendendolo piuttosto veloce rispetto alla maggior parte delle alternative . Un vantaggio, tuttavia, di unire una serie di stringhe è che alcuni rientri automatici fanno un lavoro migliore di quello che fanno, ad esempio con stringhe here-doc o con \.
Wayne Conrad,

1
Una nota, la sintassi ereditaria << - consentirà il rientro appropriato.
A. Wilson,

3

The Ruby-way (TM) da Ruby 2.3: Usa il HEREDOC ondulato <<~ per definire una stringa multilinea con newline e rientro corretto:

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

Se il rientro corretto non è un problema, le virgolette singole e doppie possono comprendere più righe in 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..."

Se le virgolette singole o doppie sono ingombranti perché ciò richiederebbe un sacco di escape, la notazione letterale della stringa percentuale % è la soluzione più flessibile:

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

Se lo scopo è quello di evitare le newline (che causeranno sia HEREDOC, virgolette e la stringa percentuale letterale), allora una continuazione di linea può essere usata inserendo una barra rovesciata \come ultimo carattere non di spazi bianchi in una linea. Questo continuerà la linea e farà concatenare Ruby da una parte all'altra (fai attenzione agli spazi all'interno della stringa tra virgolette):

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

Se usi Rails String.squish, toglierai la stringa di spazio iniziale e finale e comprimerai tutti gli spazi bianchi consecutivi (newline, tab e tutto) in un unico spazio:

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

Più dettagli:

Sintassi di Ruby HEREDOC

La Here Document Notation for Strings funziona come un modo per designare lunghi blocchi di testo incorporati nel codice. Viene avviato <<seguito da una stringa definita dall'utente (terminatore di fine stringa). Tutte le righe seguenti vengono concatenate fino a quando non viene trovato il terminatore End of String all'inizio di una riga:

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

Il terminatore di End of String può essere scelto liberamente, ma è comune usare qualcosa come "EOS" (End of String) o qualcosa che corrisponde al dominio della String come "SQL".

HEREDOC supporta l' interpolazione per impostazione predefinita o quando il terminatore EOS è racchiuso tra virgolette doppie:

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

L'interpolazione può essere disabilitata se il terminatore EOS è a virgoletta singola:

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

Una limitazione importante di <<HEREDOCè che il terminatore End of String deve trovarsi all'inizio della riga:

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

Per ovviare a questo, è <<-stata creata la sintassi. Consente al rientro del terminatore EOS di rendere il codice più gradevole. Le linee tra il <<-terminatore EOS e sono ancora utilizzate nella loro massima estensione, inclusi tutti i rientri:

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

Da Ruby 2.3, ora abbiamo l'HEREDOC che <<~rimuove gli spazi bianchi principali:

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"

Le righe vuote e le righe che contengono solo schede e spazio vengono ignorate da << ~

puts <<~EOS.inspect 
  Hello

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

Se vengono utilizzate sia le schede che gli spazi, le schede vengono considerate uguali a 8 spazi. Se la riga meno rientrata si trova nel mezzo di una scheda, questa scheda non viene rimossa.

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

HEREDOC può fare cose folli come eseguire comandi usando i backtick:

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

Le definizioni di stringa HEREDOC possono essere "impilate", il che significa che il primo terminatore EOS (EOSFOO in basso) terminerà la prima stringa e inizierà il secondo (EOSBAR in basso):

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

Non penso che nessuno lo userebbe mai come tale, ma <<EOSè davvero solo una stringa letterale e può essere messa ovunque una stringa possa essere normalmente messa:

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

Se non hai Ruby 2.3, ma Rails >=3.0, puoi usare quello String.strip_heredocche fa lo stesso<<~

# 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

Letterali a percento

Vedere RubyDoc per come utilizzare il segno percentuale seguita da una stringa in parentesi accoppiare ad esempio una %(...), %[...], %{...}, ecc o una coppia di carattere non alfanumerico come%+...+

Ultime parole

Infine, per ottenere la risposta alla domanda originale "Esiste un modo per implicare la concatenazione?" risposta: Ruby implica sempre la concatenazione se due stringhe (virgolette singole e doppie) vengono trovate back to back:

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

L'avvertenza è che questo non funziona attraverso le interruzioni di riga, perché Ruby sta interpretando una fine dell'istruzione e la conseguente linea di sole stringhe su una riga non fa nulla.


1

Risposta elegante oggi:

<<~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

C'è una differenza in <<-TEXTe <<~TEXT, il primo mantiene la spaziatura all'interno del blocco e il secondo no.

Ci sono anche altre opzioni. Come la concatenazione ecc. Ma questa ha più senso in generale.

Se sbaglio qui, fammi sapere come ...


heredoc includerà le nuove righe che non equivalgono al codice originale.
Josh

1

Come te, stavo anche cercando una soluzione che non includesse le nuove linee . (Sebbene possano essere sicuri in SQL, nel mio caso non sono sicuri e ho un grosso blocco di testo da gestire)

Questo è discutibilmente altrettanto brutto, ma è possibile eseguire il backslash-escape di nuove righe in una eredità per ometterle dalla stringa risultante:

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

Nota che non puoi farlo senza interpolazione (IE <<~'END_OF_INPUT') quindi fai attenzione. #{expressions}saranno valutati qui, mentre non saranno nel tuo codice originale. La risposta di A. Wilson potrebbe essere migliore per questo motivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.