【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

この記事は以下のブログ記事の日本語訳です。

[URI\.escape is obsolete\. Percent\-encoding your query string](https://docs.knapsackpro.com/2020/uri-escape-is-obsolete-percent-encoding-your-query-string)

# 【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

みなさんはRuby 2.7.0を使っているプロジェクトで以下の警告に遭遇しましたか？

```
warning: URI.escape is obsolete
warning: URI.encode is obsolete
```

この警告の直し方を見ていきましょう！

## 歴史について少しだけ

Ruby 2.7.0では`URI.escape`またはエイリアスメソッドの`URI.encode`を呼びだしたときに警告が出ます。これはあたかも新しく追加された警告のように見えますが、実際はなんと・・・[10年以上も](https://github.com/ruby/ruby/commit/238b979f1789f95262a267d8df6239806f2859cc)非推奨とされ続けていたのです！どうしても今までこの警告を目にしなかったんだろう？と不思議に思っている方へ。答えはこうです。これまではverboseモードでスクリプトを実行したときだけ表示されていました。そして、この仕様が最近変わりました。これがその理由です。

## じゃあなんで`URI.escape`は非推奨メソッドなの？

「URIをエスケープする」という概念は実はやっかいです。なぜならURIは多数の要素（`path`や`query`など）から成り立っており、その要素をすべて同じ方法でエスケープするわけではないからです。たとえば、`#`という文字について考えてみましょう。この文字はURIの最後に出てくるときは問題ありません（これは世間ではアンカーと呼ばれます。URI用語で言うところの`fragment`要素です）。しかし、ユーザー入力の一部に`#`が使われたとき（たとえば検索キーワードなど）は、入力値を正しく解釈するためにそれをエンコードしたいと思うはずです。同様に、クエリ文字列に`=`や`&`といった予約文字が含まれていた場合、それらが間違って区切り文字として解釈されないようにエスケープしたくなるはずです（あたかも予約文字であるかのように）。

`URI.escape`は単純に`gsub`メソッドを使って文字列全体を置換しているだけです。各要素が何であるかは区別しません。なので、上で述べたような複雑な問題は一切考慮されないのです。

## どう修正するの？

URI文字列をまるごとを受け取って、各要素の違いをうまく解釈しつつ最適なエスケープ処理を加える、というような既存の解決策はまだ見つかっていません。ですので、私が考えるに、こういう場合は各要素を別々にエンコードするのが良いと思います。最も一般的な（そして最も問題が起きやすい）ユースケースは、おそらくクエリ文字列（`query`要素）のエンコーディングでしょう。そこで、今回はこのユースケースに焦点を当てます。Rubyの`URI`モジュールには2種類の便利なメソッドが用意されているので、これを使えばうまくいきます！

## クエリ文字列をパーセントエンコードする

```ruby
URI.encode_www_form_component(string, enc=nil)
```

このメソッドは`*, -, ., 0-9, A-Z, _, a-z`だけをそのまま残し、それ以外のすべての予約文字をパーセントエンコードします。さらに、スペースも`+`に置換してくれます。以下の例を見てください。

```ruby
query = "Tom & Jerry"
query = URI.encode_www_form_component(query)
query # => "Tom+%26+Jerry"
```

```ruby
post = "So scared, but let's do this! #yolo"
post = URI.encode_www_form_component(post)
post # => "So+scared%2C+but+let%27s+do+this%21+%23yolo"
```

ご覧のとおり、適切なエスケープ処理を加えて安全なクエリ文字列を作ることができました。しかし、これだとちょっと手間がかかりすぎると感じた場合は、クエリ全体を適切に処理してくれる便利な方法があります。それがこちらです。

```ruby
URI.encode_www_form(enum, enc=nil)
```

このメソッドは先ほどのメソッドとはインターフェースが若干異なり、`Enumerable`（通常はネストした配列かハッシュ）を受け取ります。それから、そのデータを使ってクエリ文字列を構築します。このメソッドは内部で`.encode_www_form_component`を使うため、エンコーディング規則はどちらも同じです。異なるのは使い方だけです。以下の例を見てください。

```ruby
URI.encode_www_form([["search", "Tom & Jerry"], ["page", "3"]])
# => "search=Tom+%26+Jerry&page=3"

URI.encode_www_form(["search", "2 + 2 = 5"])
# => "search=2+%2B+2+%3D+5"

URI.encode_www_form(search: "why is URI.escape obsolete", category: "meta")
#=> "search=why+is+URI.escape+obsolete&category=meta"

# Shameless plug
URI.encode_www_form(q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en")
#=> "q=how+to+speed+up+your+CI%3F&tag=%23devops&tag=%23productivity&lang=en"
```

とても簡単ですね。

## Railsの場合は？

```ruby
Hash#to_query
```

（`Hash.to_param`というエイリアスメソッドもあります）

Railsは`Hash`クラスを拡張して、この便利なメソッドを追加しています。このメソッドは正しい形式でクエリ文字列を返します。値は適切にエスケープされます。

```ruby
query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query
#=> "lang=en&q=how+to+speed+up+your+CI%3F&tag%5B%5D=%23devops&tag%5B%5D=%23productivity"
```

（キーがアルファベット順にソートされる点にも注意してください）

このメソッドにはオプション引数としてネームスペースを渡すこともできます。返ってくる文字列はネームスペースが各キーを内包する形になります。たとえば、`search[q]="how to speed up your CI?"`というような形です（もちろんパーセントエンコードされますが）。

```ruby
query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query("search")
#=> "search%5Blang%5D=en&search%5Bq%5D=how+to+speed+up+your+CI%3F&search%5Btag%5D%5B%5D=%23devops&search%5Btag%5D%5B%5D=%23productivity"
```

## まとめ

短い記事ですが、みなさんが「この記事を読んだらURI文字列のエンコードに関する疑問が解消された！」と思ってくれたら嬉しいです。もし、みなさんのRuby/Railsプロジェクトでは何か別の方法を使っているなら、コメント欄にてその方法も教えてください。

そしてもし、みなさんのプロジェクトでCIのビルドが遅いことにお困りでしたら、ぜひ[Knapsack Pro](https://knapsackpro.com/?utm_source=docs_knapsackpro&utm_medium=blog_post&utm_campaign=uri-escape-is-obsolete-percent-encoding-your-query-string)をチェックしてみてください。その問題を解消します。

（翻訳はここまで）

## 訳者による補足

### 日本語や"%"が含まれる場合のエンコード結果について

クエリ文字列に日本語が含まれる場合や、文字列に`%`が含まれる場合のエンコード結果も調べてみました。
結果としては`URI.encode`を使った場合も、`URI.encode_www_form_component`や`URI.encode_www_form`を使った場合も同じでした。

```ruby
# 日本語をエンコードする場合
str = "あいう"

URI.encode(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"

URI.encode_www_form_component(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"

URI.encode_www_form([['query', str]])
#=> "query=%E3%81%82%E3%81%84%E3%81%86"
```

```ruby
# %を含む文字列をエンコードする場合
str = "10%"

URI.encode(str)
#=> "10%25"

URI.encode_www_form_component(str)
#=> "10%25"

URI.encode_www_form([['query', str]])
#=> "query=10%25"
```

### 公式リファレンスのリンク等

各メソッドの公式リファレンスはこちらです。

- [URI\.encode](https://docs.ruby-lang.org/ja/2.7.0/class/URI.html#S_ENCODE)
- [URI\.encode\_www\_form\_component](https://docs.ruby-lang.org/ja/latest/method/URI/s/encode_www_form_component.html)
- [URI\.encode\_www\_form](https://docs.ruby-lang.org/ja/latest/method/URI/s/encode_www_form.html)

また、公式リファレンスでは`URI.encode`の移行先として、`encode_www_form_component`以外にも以下のようなメソッドが紹介されています。

- [ERB::Util\.\#url_encode](https://docs.ruby-lang.org/ja/latest/method/ERB=3a=3aUtil/m/url_encode.html)
- [CGI\.escape](https://docs.ruby-lang.org/ja/2.7.0/method/URI/s/encode.html)
- [WEBrick::HTTPUtils.#escape_form](https://ruby-doc.com/stdlib/libdoc/webrick/rdoc/WEBrick/HTTPUtils.html#method-i-escape_form) 
- [WEBrick::HTTPUtils\.\#escape](https://docs.ruby-lang.org/ja/latest/method/WEBrick=3a=3aHTTPUtils/m/escape.html)