Edited at

Rails 3.2でiOS5の絵文字を扱う

More than 3 years have passed since last update.

iOS5の絵文字は4バイトのUTF-8で記述されているので、それに対応したPostgreSQLやMySQL 5.5.3以降が必要。

ここではMySQL 5.5を利用する。


MySQLでutf8mb4を扱う

前述した通り、MySQLで4バイトのUTF-8を扱うにはMySQL 5.5.3以上でキャラクタセットにutf8mb4を使う必要がある。


MySQLサーバ

たぶんキャラクタセット関係にはutf8mb4を設定しておいた方がいい。

character_set_systemcharacter_set_filesystem以外はクライアントーサーバ間の通信と保存に使われるので、適切に設定されていないとちょん切られてしまったりすると思う。要検証

mysql> show variables like 'char%';

+--------------------------+------------------------------------------------------+
| Variable_name | Value |
+--------------------------+------------------------------------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/Cellar/mysql/5.5.10/share/mysql/charsets/ |
+--------------------------+------------------------------------------------------+


ActiveRecordからの接続

で、mysql2 gemの最新リリースの0.3.11ではutf8mb4に対応していないので、未だに取り込まれていないパッチを自分であてるか、Gemfileで次のようにブランチを指定する(自分はこっちをやってる)。

gem 'mysql2', :git => 'git://github.com/tmtm/mysql2.git', :branch => 'utf8mb4'

database.ymlのencodingcharsetにutf8mb4を指定する。

development:

adapter: mysql2
charset: utf8mb4
encoding: utf8mb4
collation: utf8mb4_general_ci
reconnect: false
database: myapp_development
pool: 5
username: root
password:
socket: /tmp/mysql.sock

charsetを指定するのは、CREATE DATABASEDEFAULT CHARACTER SETに使われるから(デフォルトはutf8)。なんでencodingに統一されていないんだろう?


ユニークインデックスのバイト数制限

MySQLのユニークインデックスは767byteまでしか使えないという制限があるため、767byteわる4で191文字までしか使えない。

例えばschema_migrations.versionはvarcharでユニークインデックスがはられているが、デフォルトでは255文字が割り当てられているのでマイグレーションを実行したときにエラーが出る。

実際には14文字しか使わないので、varchar(15)に変更する。

$ bundle open activerecordからlib/active_record/connection_adapters/abstract/schema_statements.rbの419行目あたりを書き換える。

schema_migrations_table.column :version, :string, :null => false, :limit => 15

ただ本番のgemを書き換えるのも嫌なので、自分はconfig/initializers/active_record_schema_migrations_version.rbActiveRecord::ConnectionAdapters::SchemaStatements#initialize_schema_migrations_tableをそのままコピーして、該当箇所だけ書き換えている。

またschema_migrations.version以外にも自分でユニークインデックスをはる場合には:lengthオプションで191文字以下がインデックス対象になるようにする必要がある。

intとvarcharで複合インデックスをはる際には:lengthを細かく指定できるので気をつけて。

add_index :entries, [:user_id, :url],

unique: true, length: { url: (191-4) },
name: "index_on_feed_entries"


JSON

ActiveRecord::JSON::Encoding.escapeは4バイト以上の文字のエスケープに失敗するので、今はJSON gemを使ってる。

config/initializers/active_support_json_encoding.rbにこんな感じ:

module ActiveSupport

module JSON
module Encoding
class << self
def escape_with_json_gem(string)
::JSON.generate([string])[1..-2]
end
alias_method_chain :escape, :json_gem
end
end
end
end

JSON gemを使わなくても自分でちゃんとエスケープしてあげる方法もある。


参考資料


(ちなみに)WebSocket/Node.js

WebSocketにNode.jsを使う場合、JavascriptのエンジンであるV8が4バイトのUTF-8に対応していないので、URLエンコードなどの適当な変換をかけてからデータを渡してあげる。

URLエンコードだとこんな感じ:

# Rails側

encoded = Rack::Utils.escape(mb_string)

// JS側
var mbString = decodeURIComponent(encoded).replace(/\+/g, " ");


Android, iOS4はどうするの?


  • iOS4からiOS5で絵文字のキャラクタ・セットが変わったけど、iOS5ではiOS4の絵文字を表示できるので、iOS5からの絵文字をすべてiOS4のものに変換してしまうのがいいかもしれない。


    • でもそれもいつまで有効なんだろうなぁ…。



  • Androidは知らん


    • NaverのLINEががんばってる、気がする。