「Ruby」カテゴリーアーカイブ

UTF-8の4バイト文字のバリデーション

MySQLの文字セットがutf8の場合、utf-8で符号化すると4バイトになる文字(😁のような絵文字など)をセットすると、SQLモード(sql_mode)が厳密モード(STRICT_ALL_TABLES または STRICT_TRANS_TABLES のいずれかが有効)でない場合、その文字以降が切り捨てられてしまう。(警告は発生する)

4バイトUTF-8文字に対応するためには、CHARACTER SET に utf8mb4(COLLATE に utf8mb4_unicode_520_ci など utf8mb4_xxx) を指定したカラムを使用し、接続文字セットも utf8mb4 を使用する必要がある。

ちなみにRailsでは、MySQLのカラムの文字セットがutf8の場合に4バイトUTF-8文字をセットしようとすると、以下のようなエラーが発生するので、気付かないうちに文字列が切り捨てられてしまうことはない。

An ActiveRecord::StatementInvalid occurred in news#update:

Mysql2::Error: Incorrect string value: '\xF0\x9F\x98\x80\x0D\x0A' for column 'description' at row 1: UPDATE `news` SET `description` = '😀\r\n' WHERE `news`.`id` = 2
app/controllers/news_controller.rb:98:in `update'

これは特に指定していない場合、AbstractMysqlAdapter#configure_connection で、STRICT_ALL_TABLES がセッションのSQL_MODEに追加されているから。(NO_AUTO_VALUE_ON_ZERO も追加される。)

これは以下のようにして確認できる。

  • mysqlクライアントで確認
    mysql> show variables like 'sql_mode';
    +---------------+------------------------+
    | Variable_name | Value                  |
    +---------------+------------------------+
    | sql_mode      | NO_ENGINE_SUBSTITUTION |
    +---------------+------------------------+
    1 row in set (0.00 sec)
    
  • 同じデータベースに対して、rails consoleで確認
    > con = ActiveRecord::Base.connection
    > con.select_all("SHOW VARIABLES LIKE 'sql_mode'")
       (0.8ms)  SHOW VARIABLES LIKE 'sql_mode'
     => #<ActiveRecord::Result:0x00007fc6ca533728 @columns=["Variable_name", "Value"], @rows=[["sql_mode", "NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION"]], @hash_rows=nil, @column_types={}>
    

対処方法

カラムの CHARACTER SET を utf8mb4 、COLLATE を utf8mb4_xxx に変換して、接続文字セットに utf8mb4 を使用すればよいが、何らかの事情でカラムを utf8mb4 に変換できない場合は、黙って4バイトUTF-8文字以降が切り捨てられるといろいろとまずいので、4バイトのUTF-8文字をバリデーションではじくことになるだろう。

UTF-8にエンコードすると4バイトになるUnicode文字の範囲は、U+10000からU+10FFFFである。

PHPでの例

if (preg_match('/[\x{10000}-\x{10FFFF}]/u', $s) { /* ... */ }
if (preg_match('/[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]/', $s)) { /* ... */ }
preg_match_all('/[\x{10000}-\x{10FFFF}]/u', $s, $matches);
// $matches[0]に4バイトutf-8の文字の配列が格納される。

Rubyでの例

if /[\u{10000}-\u{10FFFF}]/ =~ s
  # ...
end
chars = s.scan(/[\u{10000}-\u{10FFFF}]/)
# charsに4バイトutf-8の文字の配列が格納される。

CentOS6にRuby-2.5.0をインストール

CentOS6にRuby-2.5.0をソースからインストールしようとすると、makeでエラーが発生してインストールできない。
CentOS6のgccが古いためにエラーになっている。

$ ./configure --prefix=/opt/ruby-2.5.0 --disable-install-doc
$ make
...(略)
prelude.c: In function ‘prelude_eval’:
prelude.c:204: error: #pragma GCC diagnostic not allowed inside functions
prelude.c:205: error: #pragma GCC diagnostic not allowed inside functions
prelude.c:221: error: #pragma GCC diagnostic not allowed inside functions
トップレベル:
cc1: 警告: unrecognized command line option "-Wno-self-assign"
cc1: 警告: unrecognized command line option "-Wno-constant-logical-operand"
cc1: 警告: unrecognized command line option "-Wno-parentheses-equality"
cc1: 警告: unrecognized command line option "-Wno-tautological-compare"
make: *** [prelude.o] エラー 1

Bug #14234: Failed to build on CentOS 6.9 - Ruby trunk - Ruby Issue Tracking System

Ruby-2.5.1では修正されるようだが、とりあえずsclのdevtoolsetを使ってインストールできる。

CentOS6にsclのdevtoolsetをインストール

/etc/profile.dに以下のようなファイルを作成しておいてシステムを再起動すれば、システム起動時からsclでインストールしたdevtoolsetが有効になるので、passengerのインストールやCapistranoでビルドが必要なgemのインストールもOK。(下記はdevtoolset-4の場合の例)

$ cat /etc/profile.d/enabledevtoolset-4.sh
#!/bin/bash
source scl_source enable devtoolset-4

Install Ruby 1.9.3 with libyaml on CentOS

CentOS 5.8にRuby 1.9.3をソースからインストール後、gemを実行したら、

It seems your ruby installation is missing psych (for YAML output).
To eliminate this warning, please install libyaml and reinstall your ruby.

というワーニングが出た。

libyamlはyumになかったので、LibYAML – PyYAMLからソースをダウンロードしてインストール。
その後Ruby 1.9.3を再インストールしたらワーニングは出なくなった。

Install Ruby 1.9.3 with libyaml on CentOS // Collective Idea.

gemを全部削除する

わけあって、インストールされているgemを全部削除した。
手順は、以下の通り。

  1. gem listでインストールされているgemをリストする。
  2. リストしたgemのかっこで囲まれたバージョン情報のところと改行を削除して、スペース区切りのリストにする。
  3. 以下で一括削除。
    $ sudo gem uninstall -Ixa actionpack activemodel activerecord ...(以下gemのスペース区切りリスト)

optionの意味は、

-a, --[no-]all                   Uninstall all matching versions
-I, --[no-]ignore-dependencies   Ignore dependency requirements while
                                 uninstalling
-x, --[no-]executables           Uninstall applicable executables without
                                 confirmation

もっと良い方法がありそうだけど。

cronでRVMのRubyを使う

RVMで複数のRuby環境がある場合に、Railsアプリケーションのためのcronをどうするか? Rubyのパスはフルパス指定すればいいが、gemがRVMのgemを見に行かないのが問題。RVM環境でcronを実行する必要がある。

RVM: Ruby Version Manager - Using Cron with RVM

cron用のシェルスクリプト内で使用するRVM環境をロードするには以下のようにする。

まず、環境ファイルのパスを確認

$ rvm env --path -- ruby-version[@gemset-name]

実行例

$ rvm env --path -- 1.8.7-p371
/Users/pistolfly/.rvm/environments/ruby-1.8.7-p371

cron用のシェルスクリプトで環境ファイルを読み込む

source /Users/pistolfly/.rvm/environments/ruby-1.8.7-p371

/home/pistolfly/app_dir
ruby script/runner -e production "ActiveRecord::SessionStore::Session.delete_all(['sessions.updated_at < ?', 6.hours.ago])"