まえがき
Mysql5.5以前だと4バイト以上のutf8の文字が入らない。
言い換えると3バイトまでなら入る。
Mysql5.5以降でも意識しないで使うと残念ながら入らない。。
なんじゃこりゃと思ったのでまとめておく。
ちなみにRailsから使う場合で、
MysqlはAmazon RDS上で動いている。
Amazon RDS
これがちょっと厄介で、
Amazon RDSのAPI(もしくはCLI Toolkit)が
utf8mb4に対応していない。
なので、Paramater Groupに設定して、、、ってことは出来ない。
DB作るときに指定しておけばいいんだけど、忘れると悲惨。
Rails
mysql2
大変お世話になっているmysql2ですが、
この時点でutf8mb4に対応していない。
どういうことかというと、database.ymlで
production:
adapter: mysql2
database: hogehoge
encoding: utf8mb4
って書くと怒られる。
unknown charsetとかだったかな?
対応が必要なファイルは、
mysql2/lib/mysql2/client.rb
なんだけど、github見に行くと既にutf8mb4のための更新がされてる。
そのcommit log
なので、bundlerでgit指定してやってもいいし、
乱暴な話、client.rbの該当箇所だけ修正しても良い。
ActiveSupport::JSON::Encoding
レスポンスをJSONで返したくて。
そのまんま返すんであれば読み飛ばして大丈夫。
マルチバイト文字の場合、
ActiveSupport::JSONは律儀にUnicode Escape Sequenceに変換してくれる。
一見格好いいけど、これ、\u1234みたいなごく一般的なEscape Sequenceは変換できるけど、
\u{123}とか\u{12345}みたいなEscape Sequenceはミスる。
なのでこんな感じにoverrideした。
module ActiveSupport::JSON::Encoding
class << self
def escape(string)
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
json = json.gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|
[\xF0-\xF7][\x80-\xBF]{3})+/nx) do |s|
s.unpack("U*").pack("N*").unpack("H*")[0].gsub(/.{8}/n) do |u|
/^0+(.+)$/ =~ u
($1.size == 4) ? "\\u#{$1}" : "\\u{#{$1}}"
end
end
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8)
json
end
end
end
危ないので良い子の皆さんは真似せず、
pull request送りましょう。
まとめ
こんな感じで動いた。
ただ、Stackoverflowの記事読んだりしていて、
そもそもJSONでレスポンス返すときにEscapeする必要あるの?って議論もあって、なるほどなるほどーって思いました。