Rails日本語ファイル名がSafariやIEで文字化けする対策

#Safari、IEでファイルをダウンロードする際の文字化対策
既存moduleの動作を上書きしてしまうことで解決できる。

```rb:ApplicationController.rb
class ApplicationController < ActionController::Base
  def send_file_headers!(options)
    super(options)
    match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
    encoded = URI.encode_www_form_component(match[2])
    headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
  end
end
```

ApplicationControllerにこのメソッドを定義すれば終わり！

以下は解説になります

# Railsで日本語のファイル名をDLしようとするとSafariやIEで文字化けする
この様な状況に陥ってないでしょうか。

* send_fileメソッドを用いてファイルを送ると日本語が文字化けする
* ローカル環境ではうまくいくけれど、AWSサーバなどの本番環境だけで文字化けする
* Chromeでは日本語で正常にDLできる
* SafariとIEでDLすると文字化けする
* サーバの文字コードやファイル名などをチェックしても問題なさそう



send_fileを用いてファイルをダウンロードさせようとすると、SafariやChromeにて文字化けすることがある。

```rb:download_controller.rb
def dowmload_file file_name, type:
  file_path = Rails.root.join('public', 'file', "解説書.pdf")
  send_file file_path, filename: "解説書.pdf", type: type
end
```

# 原因：Railsで実装されているDataStreaming　Moduleが対応していない
send_fileメソッドを実行した際に呼び出される、ActionControllerのDataStreamingモジュールがSafariとIEの際に、
文字コードをエンコードせずに送信するためこのバグが起きている様です。

公式リファレンス　：ActionController->DataStreaming
http://api.rubyonrails.org/classes/ActionController/DataStreaming.html

# 非推奨：alias_method_chainでのmodule書き換え
alias_method_chainを用いて既存のDataStreaming　Moduleの処理を書き換えます。

このmoduleをシステムに読み込ませてDLするとあら不思議、日本語でDLできます。

```rb:data_streaming_module.rb
module ActionController
  module DataStreaming
    def send_file_headers_with_utf8!(options)
      send_file_headers_without_utf8!(options)
      match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
      encoded = URI.encode_www_form_component(match[2])
      headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
    end
    alias_method_chain :send_file_headers!, :utf8
  end
end
```

この様なモンキーパッチを一時的にシステムに組み込むことでRailsのバージョンが上がったて修正された際には
この部分のみ取り除くことでRails自体のソースコードに手を出すという闇から逃げられます。



#alias_method_chainはRails5から非推奨なのでprependで書き換えた
module自体を書き換えることで、次回以降にprependやincludeした際にも変更が有効になります
＝＞つまり複数箇所でmoduleをincludeやprependするならこの方式で書きましょう。

```rb:application_controller.rb
class ApplicationController < ActionController::Base
  #ActionController::Baseのソースを読むとここで様々なmoduleをincludeしている
  module DataStreamPatch
    def send_file_headers!(options)
      super(options)
      match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
      encoded = URI.encode_www_form_component(match[2])
      headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
    end
  end

  module ActionController
    module DataStreaming
      prepend DataStreamPatch
    end
  end

  #moduleの動作を書き換えた上で再度includeする必要がある。
  include ActionController::DataStreaming

  ....
end
```

#複数箇所でmoduleを利用しない場合はover writeで実装したほうが単純明快
もし複数箇所でこのmoduleを別々にprependしたりという用途で使わない場合はApplicationControllerにこのように定義してしまうのが
単純明快で良い！

```rb:ApplicationController.rb
class ApplicationController < ActionController::Base
  def send_file_headers!(options)
    super(options)
    match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
    encoded = URI.encode_www_form_component(match[2])
    headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
  end
end
```

紆余曲折した結果今提供しているサービスではこの実装で動いています。


#より詳細な解説はこの記事を参照ください
なぜこのような実装になるのか、なぜこれで解決するのかはこの記事に全てまとめました。
是非こちらを参照ください。

http://qiita.com/rorensu2236/items/3f94ff653ba0c45141a7