LoginSignup
2
2

More than 5 years have passed since last update.

Rails で MySQL の返した Incorrect string のバイト表記文字列を、読める文字列に戻す

Posted at

概要

HEX表記の文字列を、UTF8の文字列に変換する方法は、HEX を取り出して pack する。

経緯

MySQL で utf8mb4 を設定しているのに、🎶 という文字の保存がエラーになった。

Mysql2::Error: Incorrect string value: '\xF0\x9F\x8E\xB6' for column 'content' at row 1: UPDATE topics SET content = '🎶', updated_at = '2016-04-22 01:53:02.596188' WHERE topics.id = 17

仕方ないので、Rails でキャッチして、エラーメッセージを返すことにしたが、エラーメッセージの変換に苦労したので、共有する。

コード

  rescue ActiveRecord::StatementInvalid => e
    if e.message.include? "Mysql2::Error: Incorrect string value:"
      message = "扱えない文字が含まれています: "
      m = e.message.match(%r{Incorrect string value: '(.*)' for})
      if m
        m1 = m[1].scan(/../).map { |b| b.to_i(16) }.reject { |x| x == 0 }.pack("C*")
        message += URI.encode(m1[0..3])
      end
      errors[:base] << message
      false
    else
      fail
    end
  end

詳細

MySQL は、エラーメッセージとして、扱えない文字を返すが、それは HEX 表記の文字列だった。

m[1] => "\\xF0\\x9F\\x8E\\xB6"

欲しいのは、UTF8 としての文字列である

"\xF0\x9F\x8E\xB6"

よって、"\x" を削除して、hex を文字列に置き換えれば、UTF-8 のバイト列が取得できる。

[178] pry(main)> a = "\\xF0\\x9F\\x8E\\xB6"
=> "\\xF0\\x9F\\x8E\\xB6"
[179] pry(main)> a.scan(/../).map{ |b| b }
=> ["\\x", "F0", "\\x", "9F", "\\x", "8E", "\\x", "B6"]
[180] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }
=> [0, 240, 0, 159, 0, 142, 0, 182]
[181] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}
=> [240, 159, 142, 182]
[182] pry(main)> a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}.pack("C*")
=> "\xF0\x9F\x8E\xB6"
[183] puts a.scan(/../).map{ |b| b.to_i(16) }.reject{|x| x == 0}.pack("C*")
🎶

一回で変換できるものはないのだろうか?

調査中

utf8mb4 を使っているので、 とかは、保存される。
なぜ🎶が保存されないのだろうか。

参考

2
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2