Akata Works

東京エンジニア。主にRuby,Go,たまにAWSとiOS。ゲーム音楽が好きです。連絡はTwitterかakata.onen@gmail.comまで

RailsとMySQLでiOSの絵文字に対応(UTF8MB4化)した話

兼ねてからちょこちょこエラーが出ていたiOSの絵文字を含んだデータにようやく対応しましたので、その備忘録です。

Railsは3.2.11、MySQLは5.6.25です。
ちなみにMySQLは5.5以降からUTF8MB4に対応しています。

はじめに

文字コードをUTF8からUTF8MB4に変更をするにあたっていくつかの注意点があります。

コレーション問題

UTF8MB4のコレーションをデフォルトにしたままだと、寿司ビール問題("🍣"と"🍺"が同じものとして扱われる)と言われる問題に遭遇し、コレーションをUTF8MB4_UNICODE_520_CIにすると、ハハパパ問題("ハ"と"パ"が同じものとして扱われる)と言われる問題に遭遇します。

そのため、ハハパパ問題を気にしない場合(そんな国内サービスはあるのか!?)はUTF8MB4_UNICODE_520_CIを、気にする場合は、コードポイントでの比較が可能なUTF8MB4_BINにする必要があります。
しかし、UTF8MB4_BINでは"A"と"a"は別のものとして扱われるので、それを許容する仕様である必要があります。

文字列のバイト長問題

UTF8が1文字あたり3バイトであるのに対し、UTF8MB4は1文字あたり4バイトになります。故に、最大で255バイトまでしか格納できないTINYTEXTカラムでは、 UTF8では85文字まで、UTF8MB4では63文字までしか格納できません。 そのため、63文字以上を格納したい場合は、より大きい型に変更する必要があります。

インデックスのバイト長問題

デフォルトでは最大インデックス長は767バイトですので、UTF8では255文字まで、UTF8MB4では191文字までしかインデックスを張れません。
しかし、こちらはMySQL 5.5.14以降からINNODB_LARGE_PREFIXオプションで最大3072バイトまで拡張できるので、そんなに大きな問題ではないです。

背景

UTF8MB4に対応していない場合、文字列が4バイトコードを含んでいるとそれ以降の部分がMySQL側で自動的にカットされてしまいます。

"てすと🍀" => "てすと"

そのため、nameにユニークキーを貼っているときに、
Hoge.where(name: "てすと🍀").first_or_create
みたいなコードを実行したら、以下のSQLが発行されるので、

select * from hoges where name = "てすと🍀" limit 1;
insert into hoges (name) values ("てすと🍀"); # 実際には`name = "てすと"`が生成される

2回目以降のコード実行でWhereの段階では同じレコードが見つからなかったのに、Createの段階では同じレコードが見つかってDuplicationエラーが発生します。

概要

サーバのcharacter_set_*や既存データベース,既存テーブルの設定はなるべく変えたくない(インデックス長とかの諸々問題を考えて)ので、HogeテーブルのName属性の文字コードをUTF8MB4に変更します。

僕の場合は、ハハパパ問題を許容できない仕様だったのでコレーションはUTF8MB4_BINを選択します。

変更方法

まずはじめに、MySQL Clientで文字化けすると確認しづらいので、とりあえずこちらも文字コードをUTF8MB4に変更します。

"my.cnf"ファイル

[client]
default-character-set = utf8mb4

[mysqld]
character-set-server = utf8mb4

次に、文字コードにUTF8MB4を使用したいカラムの文字コードとコレーションを変更します。

alter table hoges modify column name varchar(80) character set utf8bm4 collate utf8mb4_bin not null;

このとき、Rails側の文字コードMySQL側のコレーションのベースが一致しないと、下記のようなエラーが発生します。

ActiveRecord::StatementInvalid: Mysql2::Error: Illegal mix of collations (utf8mb4_bin,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation ..

なので、Rails側の文字コードの設定もUTF8MB4に変更します。
変更ファイルは"config/database.yml"ファイルです。

"config/database.yml"ファイル

development:
.
  encoding: utf8mb4
.
test:
.
  encoding: utf8mb4
.
staging:
.
  encoding: utf8mb4
.
production:
.
  encoding: utf8mb4
.

また、レプリケーションにOctopusを使用している場合は、"config/shards.yml"ファイルも同様に変更する必要があります。

これで実際に絵文字が入っていることが確認できたら完了です。
それにしても、絵文字とそれにまつわる文字コード,コレーションの関係はややこしい・・

参考URL

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.1.11 以前の Unicode サポートから現在の Unicode サポートへのアップグレード

開発環境にMacを使用しているの方は要注意!SRMコマンド!

みなさん、開発環境には何を使っていますか?Linuxですか?Macですか?はたまたWindowsですかね?
ちなみに僕は家も会社もMacを使っています。

そしてMac以外をご使用の方はこの先を読む必要はあまりないかもしれません(読んでくれるとうれしいけどね)


先日、ファイルを削除でrmコマンドを使おうとしたら、間違ってsrmと打って実行してしまったのですが、
何もエラーは出ず何も標準出力もされず通ってしまいました。

^_^[~]$ srm hoge/huga/foo/bar.txt
^_^[~]$

そして、どうせ大したことないだろうと思っていたらファイルが消えていました・・

どうもsrmコマンドはMacでファイルを完全消去するためのコマンドのようで、ゴミ箱で「確実にゴミ箱を空にする」を押したときに実行されるやつみたいです。
実行するとなんの面影もなく、ファイルが完全消滅してしまいます。

そんなコマンドがrmコマンドにsを追加するだけで簡単に使用でき、 使い方もrmコマンドとほとんど変わらないので普段Macで開発している人は要注意です!

そんなわけで早速"zshrc"に以下を追加しました。

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias srm='srm -i' # new!

今回はたまたま消していいファイルだったのでよかったんですが、大事なファイルを削除しないためにも-iオプションは付けておくようにしましょう。

参考URL

RailsとBundlerで`bundle exec`を省略した話とその時の副産物について

Railsアプリケーションの開発に使っているBundlerで一々bundle execを打つのって面倒ですよね。

調べてみるとrbenvのpluginだとかいろいろあるらしいのですが、
これまではずっとエイリアスでなんとか凌いでいました。

↓こんなやつ↓

alias rs='bundle exec rails server'
alias rc='bundle exec rails console'

まあ、これでも別にいいんですが、最近たまたまGitHubを漁っていたら、良さ気な回避方法を使用しているプロジェクトがあったのでちょっと移行してみました。

多分、仕組み的にはrbenvのpluginとそんなに変わらないんじゃないかな・・


※以下はアプリケーションルートにいる前提です

まず、bundle install時に--binstubsを付けて、railsやrakeコマンドの実行ファイルのスタブを作成しておきます。

bundle install --binstubs #{path}

※#{path}を指定しなければ'bin/‘ディレクトリ以下に作成されますが、Rails 4では元のスクリプトが上書きされるので注意してください

これで./bin/railsrailsコマンドが実行できる訳ですが、このままだとパスが通っていないのであんまり便利じゃないっす!

ただ、普通にパスを通してしまうと全てのディレクトリから実行可能になってしまうので、以下のディレクトリを作成して、".zprofile"ファイルに以下を記述します。

mkdir -p .git/safe/

多分'.git/safe/‘ディレクトリに大きな意味はないです。

export PATH=.git/safe/../../bin:$PATH

Bashなら". bash_profile"ファイルだっけ?

こうすることで、アプリケーションルートに".git/safe"ディレクトリが存在すれば、アプリケーションルートの"bin/“ディレクトリが実行パスに追加されるので、
アプリケーションルートでのみ実行可能で、かつbundle execを省略することができます。

実行パスに相対パスが来るのがなんか気持ち悪い気がしますが、仕組みが分かりやすいのでとりあえずこれに落ち着きました。
あと、僕は英語があんまり得意ではないので、もしかしたら本来の意図と違っているかもしれません・・( ̄ω ̄;)


これを応用すれば、ディレクトリレベルで実行可能なスクリプトエイリアスっぽいものが使えそうな気がしました。
環境変数もあんまり汚さないのでいいのではないでしょうか?(これが副産物です)

参考URL

今更ながらXcode 7のBitcodeについてまとめてみた

タイトルにあるように今更ながら、Xcode 7でいろいろあったBitcodeについてまとめてみました。

まずはじめに、BitcodeとはXcodeのビルドフローにおける中間言語(LLVM IR)のバイナリ表現です。 LLVM IRにはテキスト表現とバイナリ表現の2種類がありますが、そのうちのバイナリ表現になります。

また、Xcodeの基本的なビルドフローは以下のようになります。

  1. フロントエンドのClangがソースコード中間言語(LLVM IR)に変換する
  2. バックエンドのLLVMLLVM IRをマシン語に変換する

で、Xcode 7以降はデフォルトでBitcodeを含んだデータを生成するようになった感じです(もちろんBuild Settingsで無効にできます。詳しくはこの記事を)

Bitcodeを有効にすることでApple側でアーキテクチャに合ったコンパイルをしてくれるなど、メリットがあるのでなるべく有効にしておくといいと思います。


ちなみに、フレームワーク製作者の方はBitcodeに対応する場合、Build Settingsで有効にするだけだと駄目かもしれないです。詳しくは参考URLにあるのですが、xcodebuildOTHER_CFLAGS="-fembed-bitcode"を設定すると多分いけると思います。

また、実際にアプリケーションに導入してテストする際に、通常のビルドではなくアーカイブまでしてください。じゃないとBitcodeのチェックが入らないので気付かないかもしれませんよ(経験済み)

Bitcodeに対応できていない場合、以下のようなエラーが出ます。

ld: bitcode bundle could not be generated because ~ was built without full bitcode. All object files and libraries for bitcode must be generated from Xcode Archive or Install build for architecture ~
clang: error: linker command failed with exit code 1 (use -v to see invocation)

参考URL

stackoverflow.com

コンテンツ単位でボットからのアクセスを無視する

自身のサイトに訪れたユーザのアクセス情報をイベント単位で取得し、レポートを作成したり、Mixpanelを使用するとき、
クローラなどのボットが凄まじいノイズっぷりを発揮し、
レポートがメチャクチャになります。

サイト単位やページ単位でボットのアクセスをスルーする場合は、"robots.txt"ファイルを作成すればOKですが、検索結果から消滅されると困ります。

"robots.txt"については参考URLを参照してください。


そこで別の手段として、さらに細かいコンテンツ単位で指定する場合や、
"robots.txt"ファイルを無視するボット対策として、
ユーザエージェントで判断する方法があります。

例えばGooglebotの場合は、ユーザエージェントに"Googlebot"が含まれるため、 以下のコードで簡単にスルーできます。

if !request.env["HTTP_USER_AGENT"].match("Googlebot")
  # TODO:
end

同様の方法で"libwww"も簡単にスルーできたりします。

ちなみにGoogleアナリティクスには、ロボットをフィルタリングするための設定があるようですね。

参考URL

Xcodeで遭遇したエラーたち・・(逐次更新)

見つかる度に逐次更新していく予定です。

duplicate symbol OBJC_CLASS$_ (2015-09-18)

duplicate symbol _OBJC_CLASS_$_#{hoge}

同じ名前のクラスや変数,メソッドなどが定義されていれば発生します。本来はIDEの恩恵ですぐに気付くと思いますが、僕の場合はCompile Sourcesに同じファイルが含まれていたことによって発生しました。

ファイルの作り直しとかをしてたら遭遇するかもしれません。

importで"*.m"ファイルを間違えて読み込んでも発生するそうです(参考URLはてブより)

ARC forbids explicit message send of (2015-09-28)

ARC forbids explicit message send of #{'autorelease' | 'release'} 

メモリ管理方式でARCが有効になっているのに、autoreleaseメソッドやreleaseメソッドが記述されていると発生します。

TARGETSの設定からBuild Settings => Apple LLVM - Language - Objective C => Objective-C Automatic Reference CountingをNoにすることで解消します。

古いカウンタ管理方式を用いているアプリケーションを、新しいXcodeで編集したことによって発生しました。

Include of non-modular header inside framework module ${module_name} (2015-10-22)

Include of non-modular header inside framework module #{module_name}

フレームワークのモジュール内に非モジュールのヘッダファイルがインクルードされていると発生します。

TARGETの設定からBuild Settings => Apple LLVM - Language- Modules => Allow Non-modular includes in Framework ModulesをYesにし、
読み込みを許可することで解消できますが、
もしあなたがフレームワークの開発者ならば、ちゃんとmodulemapで対応したほうがいいと思います。

App Transport Security has blocked a cleartext HTTP (2015-11-19)

iOS 9からHTTPS通信が推奨されるようになったため、HTTP通信を行うとこのエラーに遭遇するかも知れません。
でも、Appleが推奨しているので、できればHTTPSに切り替えるほうがいいと思います。

一応、ATSを無効にすることでHTTPでも通信は可能です(詳しくは参考URLを見てください)

ld: bitcode bundle could not be generated because was built without full bitcode. (2015-12-07)

ld: bitcode bundle could not be generated because #{framework_path} was built without full bitcode.

使っているフレームワークがBitcodeに対応していないのに、Bitcodeを有効にしていると発生します。
Bitcodeに対応するのを待つか、TARGETSの設定からBuild Settings => Build Options => Enable BitcodeをNoにすることで無効にできます。

Xcode 7から有効になったので、Xcode 6で作ったアプリケーションをXcode 7で起動した人は見たことがあるんじゃないでしょうか?

また、Bitcodeについては別記事にまとめていますので、よかったらそちらも御覧ください。

was built for newer ios version than being linked (2018-08-09)

was built for newer ios version (${xxx}) than being linked (${yyy})

これに関しては実際に起ったものを解決したわけではないので、調べた限りでのあくまでも予想ですmm

  • xxx: リンクしているあるライブラリ中のDeployment Target
  • yyy: リンクしているあるライブラリ中またはアプリ自体のDeployment Target?

Deployment Targetに関しては過去に記事を書いたのでそれを見てもらえると......

アプリに使用するライブラリの最小動作保証バージョンがxxxなのに、アプリまたはそれ以外のライブラリでより古いバージョンの動作を保証しているってことだと思う。
なので、アプリ自体またはライブラリのDeployment Targetを調整すれば直るかな?

なおライブラリ自体のDeployment Targetを調べるには以下のコマンドを実行する。

otool -lv #{library_filepath} | less
Load command 1
      cmd LC_VERSION_MIN_IPHONEOS
  cmdsize 16
  version 7.0
      sdk n/a

参考URL

プログラミング雑記: duplicate symbol エラー

Xcodeのキャッシュのせいでエラーが出ていた

Tipsレベルですが・・

Xcodeでとあるフレームワークを入れたり消したり、バージョンを上げたり下げたりしていたら、 バージョンAを参照してるつもりが、バージョンBが参照されていました。

ちゃんとFramework Search Pathもフレームワークのパスも確認したが、 やっぱりバージョンAのものになっていました。

どうしようもなくなってしまったので先輩社員に泣きついたところ、 Xcodeが生成するキャッシュにバージョンBのものが残っており、それが利用されていることが原因かもしれないとのこと。

試しに"/Users/${user_name}/Library/Developer/Xcode/DerivedData/"ディレクトリにあるプロジェクトごとのキャッシュから、 該当するプロジェクトのディレクトリをまるごと削除したところ無事にエラーを解消することができました。

いやー先輩マジ偉大っす

みなさんもよく分からないエラーが出たらとりあえずキャッシュを消してみるといいかもしれません。


追記

どうもXcode上からでも削除することができるようです。先人の皆様ありがとうございますm( )m