railsのmysqlでemojiを使う方法

emojiを使おうと思ったら、色々と問題があるようなので、まとめます。

文字コード問題

まず、emojiはmysqlのutf8では使用できません。 mysqlでemojiを使用するにはutf8mb4にする必要があります。

これは何をしようとしているのかいうと、emojiの文字コードは1文字に4バイト使用します。 通常のutf8では、1文字に対して3バイトまでしか使用できません。 なので、mysqlで保存するためには、文字コードを変更する必要があります。

collation問題

こちらは初期設定のutf8mb4の設定でいくと、寿司ビール(🍣🍺)問題になります。 🍣と🍺が同じものとして扱われます。 こちらを別々に検索できるようにするための設定が必要になります。

これに関連してハハパパ問題というのがあります。

utf8でもきちんと設定しておかないと、はまったりします。

ハハパパ問題(参考)

ハハパパ問題は、「ハハ」「パパ」が同一の値として扱われます。 そんなことがあるのか!って思いましたが、railsmysqlではdefaultでこの設定になっています。 何も知らないと検索結果おかしい・・・みたいな罠にはまります。 では、どのようなcollationがあるかを見ていきます。

  • utf8_bin
  • utf8_general_ci
  • utf8_unicode_ci

utf8_bin 文字コードが完全に一致するもののみ返す 一番厳しいですね

utf8_general_ci アルファベットの大文字・小文字は区別しない 他は全て区別する

utf8_unicode_ci 大文字小文字/全角半角を区別しない。

「あああ」は「アアア」「ァァァ」「ぁぁぁ」にマッチする。

railsはdefaultでutf8_unicode_ciになっています。 なので、この設定は変更しましょう。

show full columns from topics;でcollationの確認をすることができます。

mysql> show full columns from topics;
+------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| Field                  | Type         | Collation       | Null | Key | Default | Extra          | Privileges                      | Comment |
+------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| id                     | int(11)      | NULL            | NO   | PRI | NULL    | auto_increment | select,insert,update,references |         |
| title                  | varchar(255) | utf8_unicode_ci | NO   |     | NULL    |                | select,insert,update,references |         |
| body                   | text         | utf8_unicode_ci | NO   |     | NULL    |                | select,insert,update,references |         |
| name                   | varchar(255) | utf8_unicode_ci | NO   |     | NULL    |                | select,insert,update,references |         |
| category_id            | int(11)      | NULL            | NO   | MUL | NULL    |                | select,insert,update,references |         |
| thumbnail_updated_at   | datetime     | NULL            | YES  |     | NULL    |                | select,insert,update,references |         |
| thumbnail_file_size    | int(11)      | NULL            | YES  |     | NULL    |                | select,insert,update,references |         |
| thumbnail_content_type | varchar(255) | utf8_unicode_ci | YES  |     | NULL    |                | select,insert,update,references |         |
| thumbnail_file_name    | varchar(255) | utf8_unicode_ci | YES  |     | NULL    |                | select,insert,update,references |         |
| created_at             | datetime     | NULL            | NO   |     | NULL    |                | select,insert,update,references |         |
| updated_at             | datetime     | NULL            | NO   |     | NULL    |                | select,insert,update,references |         |
+------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
11 rows in set (0.00 sec)

何もしていないと、無事にutf8_unicode_ciになって、ウワって結果になると思います。

default: &default
  adapter: mysql2
  collation: utf8_general_ci

これは初期設定でやるべきことですね。

もし、運用フェーズで気づいたなら、mysql側で変更しましょう。

index問題

最大のインデックス長が757バイトになっています。 utf8では255文字で、utf8mb4では191文字になります。

解決方法としては、二つあります。

  • インデックス長を増やす
  • インデックス長を減らす

減らす方法はデフォルトの動きではならなくなるので、インデックス長を増やす方が良いです。

mysql側の設定

my.cnfを設定します。

[mysqld]
innodb_file_per_table
innodb_file_format=barracuda
innodb_large_prefix = 1

rails側の設定

database.ymlの設定

default: &default
  adapter: mysql2
  charset: utf8mb4
  encoding: utf8mb4
  collation: utf8mb4_bin
  pool: 5
  timeout: 5000

絵文字を区別したいのであれば、collationの設定をutf8mb4_binにしないといけません。 区別する必要がないのであれば、utf8mb4_general_ciで大丈夫です。

ただし、このままだとmigrationの設定がうまくいかないので、挙動を変更するようにします。

config/initializers/ar_innodb_row_format.rb

ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters

    class AbstractMysqlAdapter
      def create_table_with_innodb_row_format(table_name, options = {})
        table_options = options.merge(:options => 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
        create_table_without_innodb_row_format(table_name, table_options) do |td|
          yield td if block_given?
        end
      end
      alias_method_chain :create_table, :innodb_row_format
    end

  end
end

以上です。

参考

qiita.com

qiita.com