5604


まずはじめに、私はプログラム初心者です。

環境：rails 4.1.6, ruby 2.1.3

kconvを利用してコード変換処理をしていましたが、MacOS上にvagrantを入れているローカル環境では上手く保存されず、文字化けしております。また、UTF-8環境であるため、kconvを利用せずやっても上手く行きませんでした。

vagrantは標準のUbuntuを使っています。

どう処理すれば、日本語ファイルネームを文字化けせずに保存できるのでしょうか？
ちなみに、viewには日本語で正しく表示されるため、経由した後に文字化けしていると思われます。 お手数おかけしますが、ご教示のほどお願い致します。

データの流れですが、

MacBookAir(MacOS X 10.10 Yosemite) → vagrant(Ubuntu10.3+Rails4.1.6+ruby2.1.3)
→ Macのvagrantフォルダ

となります。

  1. 今回のアップロード対象ファイル名：福ノ神仙壷四郎.jpg
  2. 実行された後のファイル名：τªÅπâÄτÑ₧_Σ╗Öσú╖σ¢¢ΘâÄ.jpg
  3. 期待する格納されるべきファイル名：福ノ神仙壷四郎.jpg
  4. ファイルの確認方法：Macターミナル及びファインダーからの目視確認

[Ruby2.1からencoding機能が実装されている](http://techracho.bpsinc.jp/baba/2013_12_26/15026)ようなので、ここらへんも考慮してみたいと思います。

[code]

    require 'kconv'
    # ＜中略＞
    def upload_process
      #アップロードファイルを取得
      file = params[:upfile]
      #ファイルのベース名（パスを除いた部分）を取得
      name = file.original_filename
      #許可する拡張子を定義
      perms = ['.jpg', '.jpeg', '.gif', '.png']
      if !perms.include?(File.extname(name).downcase)
        result = 'アップロードできるのは画像ファイルのみです。'
      #アップロードファイルのサイズが1MB以下であるか
      elsif file.size > 1.megabyte
        result = 'ファイルサイズは1MBまでです。'
      else
        #ファイル名をUTF-8→Shift-JISにエンコード
        name = name.kconv(Kconv::SJIS, Kconv::UTF8)
        #/public/docフォルダ配下にアップロードファイルを保存
        File.open("public/docs/#{name}", 'wb') { |f| f.write(file.read) }
        result = "#{name.toutf8}をアップロードしました。"
      end
      #成功／エラーメッセージを保存
      render text: result
    end
    
[/code]


CC BY-SA 3.0
2014-12-30T06:04:35.400
2817
2015-01-30T04:35:08.773
2015-01-03T11:50:47.923
724
question
ruby ruby-on-rails 文字化け
Rails4で画像ファイルをupload処理をした際、ファイルネームが文字化けする


Mac OSX ではパス名のエンコーディングに `UTF-8-MAC` という NFD(Normalization Form D)形式の UTF-8
を使っているとのことです。ですので、以下の様にして変換する必要がありそうです。

[code]

    require 'iconv'
    
    name = Iconv.iconv("UTF-8-MAC", "UTF-8", name)
    
[/code]

OSX システムが手元になく、実際に確認はしていませんので、間違っていましたらごめんなさい。

追記:

ruby 1.9 以降であれば `encode()` で `UTF-8-MAC` を使うことができますので、

[code]

    name.encode!('UTF-8-MAC', 'UTF-8')
    
[/code]

と書けます。ただ、これは今回の問題との関連性は薄いと思われますので参考程度に留めておいて下さい(後々必要になる場合もあるかもしれません)。

ところで、以下の ruby コードを実行しますと、

[code]

    s = '福ノ神_仙壷四郎.jpg'
    s.encode!('UTF-8-MAC', 'CP437')
    s.force_encoding('UTF-8')
    File.open(s, "wb").close()
    
[/code]

カレントディレクトリに以下の様なファイルができます。

[code]

    τªÅπâÄτÑ₧_Σ╗Öσú╖σ¢¢ΘâÄ.jpg
    
[/code]

UTF-8 エンコーディングである文字列 `福ノ神仙壷四郎.jpg` を `CP437` エンコーディングだとして UTF-8
へ変換してしまっていることになります。もしかしてファイルをアップロードする際の `charset` の設定(HTMLヘッダ内の `Content-
Type`)がおかしくなっているのかもしれません。


CC BY-SA 3.0
2014-12-30T08:30:24.930
2824
2015-01-02T10:22:58.293
2015-01-02T10:22:58.293
2817
answer


こちらを参照にしてみたらいかがでしょうか.

/config/initializers/carrierwave.rb

[code]

    CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
    
[/code]

[日本語のファイル名が____に置き換わってしまうのを防ぐ](http://f96q.github.io/blog/2012/08/02/carrierwave/)


CC BY-SA 3.0
2014-12-30T08:48:45.327
2825
2014-12-30T08:48:45.327
199
2817
answer


#

原因がよくわからないので僕がデバッグするならどうするか考えてみます。

まずRailsを使わず、ターミナルのコマンドなどを使って期待しているファイル名が保存できるか確認します。

[code]

    echo 日本語 > public/docs/日本語コマンド.txt
    
[/code]

つぎに、paramsの内容を使わず、期待した日本語ファイル名でファイルが保存できるか確認します。

[code]

    def upload_process
      name = "日本語nameを自分で書く.txt"
      File.open("public/docs/#{name}", 'wb') {|f| f.write('日本語') }
      render text: "{name}をアップロードしました。"
    end
    
[/code]

次に`kconv`を使わずにparamsの内容を使ってファイルが保存できるか確認します。
この際、実際に`File.open`に渡しているファイル名を確認できるようにします。

[code]

    def upload_process
      file = params[:upfile]
      name = file.original_filename
      path = "public/docs/#{name}"
    
      File.open(path, 'wb') {|f| f.write('パラメータをそのまま使っています') }
      render text: "{path}に#{name}をアップロードしました。"
    end
    
[/code]

paramsから受け取ったファイル名をそのまま使っても、期待した日本語ファイル名がテキストとして表示されていない場合、文字のエンコーディングの取り扱い方が間違っていそうなので、`kconv`などを使って、期待しているファイル名になるよう、文字コードを変換します。

[code]

    require 'kconv'
    
    def upload_process
      file = params[:upfile]
      name = file.original_filename
      path = "public/docs/#{name.toutf8}" # toutf8する
      File.open(path, 'wb') {|f| f.write('パラメータをそのまま使っています') }
      render text: "{path}に#{name}をアップロードしました。"
    end
    
[/code]

これらの確認の最中で、期待した日本語ファイル名がテキストとして表示されているのに実際のファイル名が文字化けしている場合は、ファイル名の確認方法が間違っていないかどうかなど、Railsとは関係ない部分で間違っていないかどうかを疑います。

また、`upload_process`の内容を変更して確認する前に、都度`public/docs`ディレクトリの内容を削除しておくとよいでしょう。
なぜなら、`upload_process`にバグがあり、ファイルが保存されない場合に、前回のファイルが残っていると「ちゃんと保存されてるけど前と結果がかわらないなぁ」と勘違いをし、バグがあって保存されていないことに気がつかないかもしれないからです。

参考まで。


CC BY-SA 3.0
2015-01-01T03:36:07.783
2882
2015-01-01T03:36:07.783
2599
2817
answer


一般論として、アップロードされたファイル名をそのままディスク上のファイル名として保存するのは避けるべきです。特にマルチバイト文字の扱いに知識が無いならなおさらです。また、Railsは何もしなければ普通UTF-8文字列になってるはずなので、kconvとかString#encodeとかはとりあえず忘れてください。

代案として、ファイル名をエンコードして保存する方法と、DBにファイル名を保持しておく方法を紹介します。

## エンコードする方法

[code]

    def encode_filename(filename)
      filename.unpack("H*")[0]
    end
    
    def upload_process
      file = params[:upfile]
      name = file.original_filename
      File.open("public/docs/#{encode_filename(name)}", 'wb') { |f| f.write(file.read) }
      result = "#{name}をアップロードしました。"
      render text: result
    end
    
[/code]

"あいうえお.txt"がe38182e38184e38186e38188e3818a2e747874で保存されます。

読み込むときも同様に

[code]

    filename = "あいうえお.txt"
    open(encode_filename(filename)).read
    
[/code]

保存されているファイル名から元のファイルを取り出すには

[code]

    def decode_filename(filename)
      s = [filename].pack("H*")
      s.force_encoding('utf-8')
      s
    end
    
    decode_filename("e38182e38184e38186e38188e3818a2e747874") # => "あいうえお.txt"
    
[/code]

## DBにファイル名を保存しておく方法

モデルの作成  
`$ rails g model attachment filename`

[code]

    class Attachment < ActiveRecord::Base
      SAVE_PATH = "public/docs/"
      attr_reader :file
    
      after_create :save_file
    
      def file=(file)
        @file = file
        self.filename = file.original_filename
      end
    
      def save_file
        open(SAVE_PATH + self.id.to_s, "wb") {|f| f.write(file.tempfile.read) }
      end
    
      def load_file
        open(SAVE_PATH + self.id.to_s).read
      end
    end
    
[/code]

controllerでは

[code]

     Attachment.create(file: params[:up_file]) 
     #ファイルの保存。public/docs/1に保存されます。モデルのidなので連番で増えていきます
    
     attachment = Attachment.find(1)
     attachment.filename #=> "あいうえお.txt"
     attachment.load_file #=> "ファイルの中身"
    
[/code]

のように使えます。

実際にはファイルだけを扱うのではなく他の情報と一緒に扱うのでしょうからそちらのモデルに関連を持たせればいいでしょう


CC BY-SA 3.0
2015-01-30T04:35:08.773
5604
2015-01-30T04:35:08.773
5793
2817
answer