Rails 3.2でiOS5の絵文字を扱う


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_system `と`character_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](https://rubygems.org/gems/mysql2)の最新リリースの0.3.11ではutf8mb4に対応していないので、[未だに取り込まれていないパッチ](https://github.com/brianmario/mysql2/commit/f535df862ccbb098d99bf4fc722c037be688dcc6)を自分であてるか、Gemfileで次のようにブランチを指定する（自分はこっちをやってる）。

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

database.ymlの`encoding`と`charset`にutf8mb4を指定する。

```yaml
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 DATABASE`で`DEFAULT CHARACTER SET`に使われるから（デフォルトはutf8）。なんで`encoding`に統一されていないんだろう？

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

[MySQLのユニークインデックスは767byteまでしか使えない](http://d.hatena.ne.jp/tanamon/20090930/1254332746)という制限があるため、767byteわる4で191文字までしか使えない。

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

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

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

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

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

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

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

```rb
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](https://rubygems.org/gems/json)を使ってる。

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

```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を使わなくても[自分でちゃんとエスケープしてあげる方法](http://qiita.com/items/d3fe732d7bc8425f4323)もある。

#### 参考資料

* [rails mysqlでiPhoneアプリの絵文字対応 ](http://d.hatena.ne.jp/yutadayo/20120630/1341070679)
* [How do you deal with the conflict between ActiveSupport::JSON and the JSON gem?](http://stackoverflow.com/questions/683989/how-do-you-deal-with-the-conflict-between-activesupportjson-and-the-json-gem/3285570#3285570)
* [Emoji and Rails JSON output issue](http://blog.sosedoff.com/2012/04/26/emoji-and-rails-json-output-issue/)

## （ちなみに）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ががんばってる、気がする。