Ruby: Puis-je écrire une chaîne multi-lignes sans concaténation?

397

Existe-t-il un moyen de rendre cela un peu meilleur?

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'

Par exemple, existe-t-il un moyen d'impliquer la concaténation?

Des morts-vivants
la source
28
Faites attention aux attaques par injection SQL. :)
Roy Tinker

Réponses:

595

Il y a des éléments dans cette réponse qui m'ont aidé à obtenir ce dont j'avais besoin (concaténation multi-lignes facile SANS espace supplémentaire), mais comme aucune des réponses réelles ne l'avait, je les compile ici:

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"

En bonus, voici une version utilisant une drôle de syntaxe HEREDOC (via ce lien ):

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

Ces derniers concerneraient principalement des situations nécessitant plus de flexibilité dans le traitement. Personnellement, je ne l'aime pas, cela place le traitement dans un endroit étrange par rapport à la chaîne (c'est-à-dire devant, mais en utilisant des méthodes d'instance qui viennent généralement après), mais il est là. Notez que si vous indentez le dernier END_SQLidentifiant (ce qui est courant, car il s'agit probablement d'une fonction ou d'un module), vous devrez utiliser la syntaxe avec trait d'union (c'est-à-dire p <<-END_SQLau lieu de p <<END_SQL). Sinon, le blanc en retrait induit l'interpréteur comme une continuation de la chaîne.

Cela n'économise pas beaucoup de frappe, mais cela me semble plus agréable que d'utiliser des signes +.

De plus (je dis dans une édition, plusieurs années plus tard), si vous utilisez Ruby 2.3+, l'opérateur << ~ est également disponible , ce qui supprime l'indentation supplémentaire de la chaîne finale. Vous devriez pouvoir supprimer l' .gsubinvocation, dans ce cas (bien que cela puisse dépendre à la fois de l'indentation de départ et de vos besoins finaux).

EDIT: Ajout d'un plus:

p %{
SELECT * FROM     users
         ORDER BY users.id DESC
}.gsub(/\s+/, " ").strip
# >> "SELECT * FROM users ORDER BY users.id DESC"
A. Wilson
la source
2
C'est une vieille question MAIS il y a soit une erreur dans la réponse, soit un changement de syntaxe depuis. p <<END_SQLdevrait être p <<-END_SQLSinon, c'est la réponse. en option, vous pouvez supprimer les espaces blancs de premier plan avec l'opérateur squiggly HEREDOC,<<~END_SQL
jaydel
Ce n'est qu'une erreur si l'identificateur de fin est mis en retrait (le trait d'union indique à l'interpréteur ruby ​​de supprimer les espaces avant de déterminer l'identificateur de fin). Je peux cependant mettre une note à ce sujet. De plus, le ~ n'est pas nécessaire, gsub \ s + et strip suppriment déjà le premier espace blanc.
A. Wilson
Ajouter <<~à la réponse serait bien, a fini par faire des recherches à partir de là. Personnellement, j'utilise <<~MSG.strip ... MSGce qui enlève aussi le dernier \n.
Qortex
1
Quand j'ai écrit cette réponse (il y a neuf ans, sheesh!), Ruby était en 1.9, et << ~ (évidemment) n'a été introduit qu'en 2.3. Quoi qu'il en soit, histoire ancienne mise à part, je vais la mettre, merci de l'avoir évoquée.
A. Wilson
Merci d'être l'une des rares réponses qui n'ajoute pas de nouvelles lignes supplémentaires, ce que j'essayais d'éviter lorsque j'ai trouvé cette question.
Josh
174

Dans ruby ​​2.0, vous pouvez maintenant simplement utiliser %

Par exemple:

SQL = %{
SELECT user, name
FROM users
WHERE users.id = #{var}
LIMIT #{var2}
}
Robbie Guilfoyle
la source
14
Fonctionne également dans Ruby 1.9.3.
Andy Stewart
26
Une chaîne créée avec cette syntaxe comprendra à la fois les sauts de ligne et tout retrait ajouté aux lignes suivantes.
James
C'est encore mieux que << EOT ...... EOT (ici document)! il effectue également une interpolation si nécessaire.
Nasser
1
@Nasser A heredoc effectue également l'interpolation.
Fund Monica's Lawsuit
3
Si vous utilisez Rails, l'appel squishsur la sortie devrait être utile.
Jignesh Gohel
167

Oui, si cela ne vous dérange pas l'insertion de nouvelles lignes supplémentaires:

 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'

Vous pouvez également utiliser un hérédoc :

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
la source
87
Vous pouvez également utiliser%Q(...)
BaroqueBobcat
3
@Zombies: les retours à la ligne sont généralement autorisés dans les instructions SQL et sont simplement traités comme des espaces blancs ordinaires.
Mark Byers
2
voir ma réponse ci-dessous pour un exemple, vous pouvez simplement utiliser% maintenant.
Robbie Guilfoyle
4
Vous pouvez également utiliser%(...)
zéro diviseur
1
Il est important de garder à l'esprit si vous ajoutez intentionnellement des espaces de fin et utilisez l'une de ces solutions, c'est que votre éditeur peut supprimer automatiquement l'espace de fin lors de l'enregistrement du fichier. Bien que je préfère normalement ce comportement, cela m'a provoqué plusieurs fois des problèmes inattendus. Une solution consiste à écrire votre chaîne multi-lignes comme la façon dont l'OP a fait dans la question.
Dennis
50

Il existe plusieurs syntaxes pour les chaînes multilignes comme vous l'avez déjà lu. Mon préféré est le style 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 chaîne multiligne commence par% q, suivie d'un {, [ou (, puis se termine par le caractère inversé correspondant.% Q ne permet pas l'interpolation;% Q le fait, vous pouvez donc écrire des choses comme ceci:

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}

En fait, je n'ai aucune idée de la façon dont ces types de chaînes multi-lignes sont appelés alors appelons-les simplement multilignes Perl.

Notez cependant que si vous utilisez des multilignes Perl ou des heredocs comme l'ont suggéré Mark et Peter, vous vous retrouverez avec des espaces blancs potentiellement inutiles. Dans mes exemples et leurs exemples, les lignes "from" et "where" contiennent des espaces blancs de début en raison de leur retrait dans le code. Si cet espace n'est pas souhaité, vous devez utiliser des chaînes concaténées comme vous le faites maintenant.

Hongli
la source
4
de # {table_names} ne fonctionnerait pas dans cet exemple, comme vous avez utilisé% q {}, cela fonctionnerait si vous
utilisiez
2
Mon préféré dans cette veine est juste% {chaîne super multiligne avec support d'interpolation}
Duke
les chaînes produites à partir de la %qfamille incluront les sauts de ligne qui ne sont pas équivalents au code d'origine.
Josh
29

Il vaut parfois la peine de supprimer de nouveaux caractères de ligne \ncomme:

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
la source
5
ce sont des rails à base non rubis
a14m
23

Vous pouvez également utiliser des guillemets doubles

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

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

Si nécessaire pour supprimer les sauts de ligne "\ n" utilisez une barre oblique inverse "\" à la fin de chaque ligne

juliangonzalez
la source
5
Vous pouvez obtenir le même résultat avec les guillemets doubles singuliers. Il n'y a rien de tel que les doubles guillemets doubles dans Ruby. Il les interprète simplement comme "" + "double quotes with some content" + "".
rakvium
Oui, mais `" "+" \ n bonjour \ n "+" "Ça a l'air bizarre
juliangonzalez
1
Oui, cela semble bizarre, et c'est pourquoi il n'y a aucune raison d'ajouter des guillemets supplémentaires lorsque vous pouvez simplement utiliser les guillemets singuliers avec le même résultat.
rakvium
Oui, je voulais dire le signe plus. Les guillemets doubles sans cela semblent très bien, ils sont lisibles et plus faciles à repérer au lieu d'un guillemet simple, qui devrait être utilisé sur des chaînes de ligne simples.
juliangonzalez
1
Je veux dire que ça a l' "x"air mieux et fonctionne plus rapidement que """x"""(ce qui est fondamentalement le même que ""+"x"+"") ou """""x"""""(ce qui est le même que "" + "" + "x" + "" + ""). C'est Ruby, pas Python, où vous utilisez """plutôt que "lorsque vous avez besoin d'une chaîne multi-lignes.
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
la source
1
l'utilisation de heredoc sans le «-», comme dans «<< - eos», inclura les espaces de leader supplémentaires. voir la réponse de Mark Byers.
ives
heredoc inclura les nouvelles lignes qui ne sont pas équivalentes au code original.
Josh
15

Autres options:

#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
la source
1
Devrait changer <<EOMpour <<-EOM, non?
kingPuppy
Peut-être, cela semblait fonctionner pour mon <<-EOFexemple. Je suppose que dans les deux cas, cela fonctionne.
Alex Cohen
heredoc inclura les nouvelles lignes qui ne sont pas équivalentes au code original.
Josh
11

Récemment, avec les nouvelles fonctionnalités de Ruby 2.3, la nouvelle squiggly HEREDOCvous permettra d'écrire nos chaînes multilignes de manière agréable avec un changement minimal, donc l'utilisation de cela combiné avec le .squish(si vous utilisez des rails) vous permettra d'écrire multiligne de manière agréable! en cas de simple utilisation de rubis, vous pouvez faire un <<~SQL.split.join(" ")qui est presque le même

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

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

Mark Jad
la source
squish is rails, not ruby
Josh
1
@ Josh, ouais tu as raison, a mis à jour la réponse, cheers.
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'

<< est l'opérateur de concaténation des chaînes

Dom Brezinski
la source
2
+est l'opérateur de concaténation régulier, <<est l' opérateur d'ajout sur place . L'utilisation d'effets secondaires sur un littéral fonctionne ici (la première chaîne est modifiée deux fois et renvoyée) mais à mon humble avis, c'est bizarre et me fait faire une double prise, où ce +serait parfaitement clair. Mais peut-être que je suis juste nouveau pour Ruby ...
Beni Cherniavsky-Paskin
Cela ne fonctionnera pas si frozen_string_literalest activé
Raido
6

Si vous faites l' esprit des espaces supplémentaires et des sauts de ligne, vous pouvez utiliser

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

(utilisez% W pour les chaînes interpolées)

UncleGene
la source
J'aime beaucoup celui-ci car il permet beaucoup plus de combinaisons d'utilisation.
schmijos
1
Cela va écraser plusieurs espaces adjacents en un seul. (Son écrasement de la nouvelle ligne + l'indentation suivante est une victoire ici, mais au milieu de la ligne, cela pourrait être surprenant.)
Beni Cherniavsky-Paskin
5

Pour éviter de fermer les parenthèses pour chaque ligne, vous pouvez simplement utiliser des guillemets doubles avec une barre oblique inverse pour échapper à la nouvelle ligne:

"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
la source
C'est l'une des rares réponses sur cette page qui répond réellement à la question!
Josh
4
conn.exec [
  "select attr1, attr2, attr3, ...",
  "from table1, table2, table3, ...",
  "where ..."
].join(' ')

Cette suggestion a l'avantage sur les documents et les chaînes longues ici que les auto-indenters peuvent indenter chaque partie de la chaîne de manière appropriée. Mais cela a un coût d'efficacité.

Aidan Cully
la source
@Aidan, vous pouvez remplacer les virgules par des barres obliques inverses (à la C) et aucune jointure (ou tableau) ne sera nécessaire: l'interpréteur concaténera les chaînes au moment (je pense) de l'analyse, ce qui le rend assez rapide par rapport à la plupart des alternatives . Un avantage, cependant, de joindre un tableau de chaînes est que certains auto-indenteurs font un travail plus agréable qu’avec, par exemple, des chaînes ici-doc ou avec \.
Wayne Conrad
1
Une remarque, la syntaxe heredoc << - permettra une indentation appropriée.
A. Wilson
3

Le Ruby-way (TM) depuis Ruby 2.3: Utilisez le HEREDOC ondulé <<~ pour définir une chaîne multi-lignes avec des retours à la ligne et une indentation appropriée:

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 l'indentation appropriée n'est pas un problème, les guillemets simples et doubles peuvent s'étendre sur plusieurs lignes dans 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 les guillemets simples ou doubles sont lourds car cela nécessiterait beaucoup d'échappements, la notation littérale en pourcentage % est la solution la plus 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 le but est d'éviter les sauts de ligne (que provoqueront à la fois HEREDOC, les guillemets et le pourcentage de littéral de chaîne), une continuation de ligne peut être utilisée en mettant une barre oblique inverse \comme dernier caractère non blanc dans une ligne. Cela continuera la ligne et obligera Ruby à concaténer les chaînes dos à dos (attention aux espaces à l'intérieur de la chaîne citée):

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 vous utilisez Rails String.squish, la chaîne des espaces de début et de fin sera supprimée et tous les espaces consécutifs (sauts de ligne, tabulations, etc.) seront réduits en un seul espace:

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

Plus de détails:

Syntaxe Ruby HEREDOC

Le travail Here Document Notation for Strings est un moyen de désigner de longs blocs de texte en ligne dans le code. Il est démarré par <<suivi d'une chaîne définie par l'utilisateur (le terminateur de fin de chaîne). Toutes les lignes suivantes sont concaténées jusqu'à ce que le terminateur de fin de chaîne soit trouvé au tout début d'une ligne:

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

Le terminateur de fin de chaîne peut être choisi librement, mais il est courant d'utiliser quelque chose comme "EOS" (fin de chaîne) ou quelque chose qui correspond au domaine de la chaîne tel que "SQL".

HEREDOC prend en charge l' interpolation par défaut ou lorsque le terminateur EOS est entre guillemets:

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

L'interpolation peut être désactivée si le terminateur EOS est entre guillemets simples:

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

Une restriction importante de la <<HEREDOCest que le terminateur de fin de chaîne doit être au début de la ligne:

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

Pour contourner cela, la <<-syntaxe a été créée. Il permet de mettre en retrait le terminateur EOS pour rendre le code plus agréable. Les lignes entre le <<-terminateur et EOS sont toujours utilisées dans leur extension complète, y compris toutes les indentations:

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

Depuis Ruby 2.3, nous avons maintenant le squiggly HEREDOC <<~supprime les espaces blancs principaux:

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"

Les lignes vides et les lignes qui ne contiennent que des tabulations et de l'espace sont ignorées par << ~

puts <<~EOS.inspect 
  Hello

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

Si des tabulations et des espaces sont utilisés, les tabulations sont considérées comme égales à 8 espaces. Si la ligne la moins indentée se trouve au milieu d'un onglet, cet onglet n'est pas supprimé.

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

HEREDOC peut faire des choses folles telles que l'exécution de commandes à l'aide de backticks:

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

Les définitions de chaîne HEREDOC peuvent être "empilées", ce qui signifie que le premier terminateur EOS (EOSFOO ci-dessous) terminera la première chaîne et démarrera la seconde (EOSBAR ci-dessous):

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

Je ne pense pas que quiconque l'utiliserait jamais en tant que tel, mais <<EOSc'est vraiment juste un littéral de chaîne et peut être mis là où une chaîne peut normalement être mise:

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 vous n'avez pas Ruby 2.3, mais Rails >=3.0, vous pouvez utiliser String.strip_heredocce qui fait la même chose 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

Pourcentages de chaînes de caractères

Voir RubyDoc pour savoir comment utiliser le signe de pourcentage suivi d'une chaîne dans une paire de parenthèses comme un %(...), %[...],%{...} , etc. ou une paire de tout caractère non alphanumérique tel%+...+

Derniers mots

Enfin, pour obtenir la réponse à la question initiale "Existe-t-il un moyen d'impliquer la concaténation?" répondu: Ruby implique toujours la concaténation si deux chaînes (simples et doubles entre guillemets) se trouvent dos à dos:

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

La mise en garde est que cela ne fonctionne pas entre les sauts de ligne, car Ruby interprète une fin de déclaration et la ligne conséquente de chaînes uniquement sur une ligne ne fait rien.

Christopher Oezbek
la source
1

Réponse élégante aujourd'hui:

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

Il y a une différence dans <<-TEXTet<<~TEXT , l'ancien conserve l'espacement à l'intérieur du bloc et ce dernier ne le fait pas.

Il existe également d'autres options. Comme la concaténation, etc., mais celle-ci a plus de sens en général.

Si je me trompe ici, faites-moi savoir comment ...

Sandip Mane
la source
heredoc inclura les nouvelles lignes qui ne sont pas équivalentes au code original.
Josh
1

Comme vous, je cherchais également une solution qui n'inclut pas les retours à la ligne . (Bien qu'ils puissent être sûrs en SQL, ils ne le sont pas dans mon cas et j'ai un gros bloc de texte à traiter)

C'est sans doute tout aussi laid, mais vous pouvez annuler les sauts de ligne dans un hérédoc pour les omettre de la chaîne résultante:

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

Notez que vous ne pouvez pas le faire sans interpolation (IE <<~'END_OF_INPUT'), alors soyez prudent. #{expressions}seront évalués ici, alors qu'ils ne le seront pas dans votre code d'origine. La réponse de A. Wilson peut être meilleure pour cette raison.

Josh
la source