Gemの作り方

RightCSVで久々にGemを作ったので、最近のGemの作り方をメモっときます。

GitHub上にリポジトリを作って git clone

別に最初にやらなくてもいいですけど、どうせ GitHub で公開するのなら最初にやっといた方が楽です。READMEやLICENSEファイルも作ってくれますし。

% git clone git@github.com:tmtm/rightcsv.git

bundle gem で雛形を作る

% bundle gem rightcsv --test 
Creating gem 'rightcsv'...
      create  rightcsv/Gemfile
      create  rightcsv/lib/rightcsv.rb
      create  rightcsv/lib/rightcsv/version.rb
      create  rightcsv/rightcsv.gemspec
      create  rightcsv/Rakefile
    conflict  rightcsv/README.md
Overwrite /tmp/rightcsv/README.md? (enter "h" for help) [Ynaqdh] y
       force  rightcsv/README.md
      create  rightcsv/bin/console
      create  rightcsv/bin/setup
      create  rightcsv/.gitignore
      create  rightcsv/.travis.yml
      create  rightcsv/.rspec
      create  rightcsv/spec/spec_helper.rb
      create  rightcsv/spec/rightcsv_spec.rb
Initializing git repo in /tmp/rightcsv

ライブラリやテストコードやREADMEを書く

% cd rightcsv
...

テスト

% rake spec

gemspec を書いて gem を作る

*.gemspec の中の TODO 部分を書き換えてから、rake build を実行。

% rake build
rightcsv 0.1.0 built to pkg/rightcsv-0.1.0.gem.

RubyGems に公開

% rake release

最近は楽ですねー。昔はいろいろ手作業してたのに。

Rubyのnilを返さないCSVライブラリ

RubyのCSVライブラリは何故か空フィールドについてnilを返します(「"」で括られていると空文字列を返します)。

require 'csv'

CSV.parse('a,,"","1,2"') do |row|
  p row
end
#=> ["a", nil, "", "1,2"]

なのでそれを考慮していないと NoMethodError(いわゆるぬるぽ)が起きたりして酷い目にあったりします (><)

require 'csv'

CSV.parse('a,,"","1,2"') do |row|
  row.each do |col|
    p col.length
  end
end
#=> 1
#=> test.rb:5:in `block (2 levels) in <main>': undefined method `length' for nil:NilClass (NoMethodError)

CSVの「"」は文字列中に「"」や「,」や改行が含まれる場合のエスケープ記号なので、「,"",」と「,,」は同じ値になるべきです。 「,"abc",」と「,abc,」はどちらも文字列abcになるのに、「,"",」と「,,」に違いがあるのがおかしいです。 「,"",」と「,,」とで異なる扱いをしたい場合なんてありません。

CSV ライブラリは変換器を設定しておくと値を変換できるという機能を持っているのですが、残念ながら nil に対しては働きません。1

CSV.parse_line("abc,,def", converters: ->(v){v.to_s.upcase})
#=> ["ABC", nil, "DEF"]

ということで、CSV をラップして nil の代わりに空文字列を返すようなライブラリを作ってみました。 ついでに CSV 生成時にも nil と "" の違いが無いようにしてあります。

github.com

require 'rightcsv'

CSV.parse('a,,"","1,2"')       #=> [["a", nil, "", "1,2"]]
RightCSV.parse('a,,"","1,2"')  #=> [["a", "", "", "1,2"]]

CSV.generate_line(['a', nil, '', '1,2'])       #=> 'a,,"","1,2"'
RightCSV.generate_line(['a', nil, '', '1,2'])  #=> 'a,,,"1,2"'

CSV が nil じゃなくて空文字列を返すようにするリクエスト Feature #12839 も発行されてますが、これが採用されたとしてもRubyの過去のバージョンでは使えないし、互換性維持のためにデフォルトでこの動きになることは考えにくいので、作ってみました。

このライブラリが不要になればそれに越したことはないです。


  1. これはバグ登録されていて Bug #11126、さっき見てみたら開発版で直されてました。

第15回 SQLアンチパターン読書会に参加しました

8/2 NSEGの「SQLアンチパターン読書会」の第15回に参加しました。

nseg.connpass.com

隔週の水曜日の夜に開催しています。だいたいいつも3〜4人くらいでこじんまりとやってます。今回は4人でした。

一人が1〜2ページくらいを音読して、気になったことをみんなで話したりして、また次の人が読んで…を繰り返しすという感じでやってます。

今回は24章を読みました。以下、感想等。

24章 マジックビーンズ(魔法の豆)

自分はこの「モデルがアクティブレコードそのもの」はアンチパターンだというのにとても共感していて、一番の弊害は「アクティブレコードはCRUD機能を公開してしまう」だと思っています。 あまり賛同は得られませんでしたが…。


次回は 8/23 18:30〜 です。次回最終回。

nseg.connpass.com

第14回 SQLアンチパターン読書会に参加しました

NSEGの「SQLアンチパターン読書会」の第14回に参加しました。

nseg.connpass.com

隔週の水曜日の夜に開催しています(が、今回は私の都合で1日ずらしてもらいました)。だいたいいつも3〜4人くらいでこじんまりとやってます。今回は5人でした。

一人が1〜2ページくらいを音読して、気になったことをみんなで話したりして、また次の人が読んで…を繰り返しすという感じでやってます。

今回は22章と23章を読みました。以下、その場でされた会話の記憶。

22章 シー・ノー・エビル(臭いものに蓋)

「この会社変な電話ばっかり掛かってくるな」

「詳細な状況を聞こうとあれこれ質問すると、「おたくの製品のデバッグをさせる気か!」ってのはあった」

「このPHPのPDOってライブラリ、例外発生したり戻り値で失敗を表したり一貫性なさすぎじゃね?」

「そういえばGo言語は例外機構ないらしい」

「メソッド内で発生した例外を握りつぶしちゃうプログラムは見たことある。あれはどういう心理なんだろう。自分の担当メソッドで一切のエラーを発生させちゃいけないって思い込みなのかな」

23章 ディプロマティック・イミュニティ(外交特権)

「リアル トラックナンバー1だ!」

「データベースに関するドキュメントは書かないなぁ。ER図は必要ならツールで生成すればいいしそもそも必要にならない。テーブルや関連の説明なんかはプログラム中にコメントで書いちゃう」

「最近はフレームワークのマイグレーションの仕組みを使うからあんまり意識しないよね」

「Railsはその文化を作ったところがすごい」

「Djangoはモデルから自動的にマイグレーションファイルを生成するからマイグレーションファイルを手で書くことはない」


次回は 8/2 18:30〜 です。これでこの本は最後になるかも。

nseg.connpass.com

第12回 SQLアンチパターン読書会に参加しました

NSEGの「SQLアンチパターン読書会」の第12回に参加しました。

nseg.connpass.com

隔週の水曜日の夜に開催しています。だいたいいつも3〜4人くらいでこじんまりとやってますが、今回は7人でした。

一人が1〜2ページくらいを音読して、気になったことをみんなで話したりして、また次の人が読んで…を繰り返しすという感じでやってます。

今回は18章と19章を読みました。以下、その場で出た話を記憶を頼りに。

18章 インプリシットカラム(暗黙の列)

「冒頭のJOIN結果に同じカラムがあって予期しない値が取れたってのはこの前ハマった」

tmtms.hatenablog.com

「ワイルドカードは普通使わないよねぇ」

「Software Design 7月号の特集にも軽く書いたわー」

19章 リーダブルパスワード(読み取り可能パスワード)

「高いレイヤーの話になってきたので読みやすくなってきた」

「パスワードリセット用のURLは短縮URL使って短くできないかな」

「短縮URLのアルゴリズムにもよるけど、単純な短い文字列とURLのマッピングだと安全でなくなるのでは」

終了後

「DockerのコンテナIDってシェルで補完対象にならないかな」

「それ、Ubuntu の zsh だとできるで」


次回は 7/5 18:30〜 です。興味がある人は参加してみるのもいいと思います。

nseg.connpass.com

MySQLの日本語コレーション

4月にMySQLの日本語コレーションについて語り合う場に呼ばれていろいろ話を聞いてきました。すぐにブログを書こうと思ったんですが、はや2ヶ月経過…。

ときどき、自分がMySQLの文字コードに関して発表する際に、次のようなスライドをいれてるんですが、

https://image.slidesharecdn.com/mysql-2017-170520113534/95/mysql-2017-55-638.jpg

MySQL 8.0 でとうとう日本語コレーションが入ることになったのに、なんか期待してたのと違いました。

https://image.slidesharecdn.com/mysql-2017-170520113534/95/mysql-2017-58-638.jpg

で、その辺の話を聞きました(2ヶ月も経ってるのでうろ覚え)。

Q. わざわざ日本語ロケール作るんだったら日本人が扱いやすいロケールにしてほしい

utf8mb4_ja_0900_as_csはMySQLが独自に考えたものではない。Unicode規格に従っている。過去にいろいろ独自にやって失敗してきてるので、もう独自にやるのは避けたい。

ai(accent insensitive)で「ハ」=「パ」=「バ」になるのも、ci(case insensitive)で「や」=「ゃ」になるのもUnicodeに従っている。

Q. ja_0900_as_cs0900_as_cs と何が違うの?

utf8mb4_ja_0900_as_cs は日本語固有の規則に従う。CLDR参照。

長音記号「ー」の順序が前の字によって異なる。たとえば「アー」は「アイ」よりも前だが「ウー」は「ウイ」よりも後。

そんな凝ったことしてたのか…。

その他

コレーションは xml ファイルを置くことで独自に定義できるので、標準のコレーションが気に入らないのなら自分で定義すればいい。

マルチバイト文字はリコンパイルしないと組み込めないと思ってましたが、それは文字セットの話でコレーションについてはコンパイル不要とのことでした。

ちゃんとマニュアルにも書いてありました。10.4.4 Unicode 文字セットへの UCA 照合順序の追加

ja_0900_as_cs はひらがなとカタカナを区別しないが、区別したい場合のために ja_0900_as_cs_ks というコレーションを作ろうとしている。

うーん、個人的にはそこまで区別したいんだったら utf8mb4_bin でいいかな…。


MySQLは標準(Unicode)に従ってるだけなので、独自におかしなことをしているわけではないということでした。

そしてUnicodeの日本語の照合順序はJIS X 4061が元なので、日本語の扱いがおかしいと日本人がMySQLに対して言うのは「お前がゆーな!」状態でした。申し訳ありませんでした!

あと最近知ったのですが、ja_0900_as_cs の場合は、漢字の順番もちゃんとJIS順になってました。 試してみると、UTF-8の文字コード順ではなくJISコード順(音読みの順)になっていることがわかります。

mysql> select hex(s),s from ja_0900_as_cs order by s;
+--------+------+
| hex(s) | s    |
+--------+------+
| E4BA9C | 亜   |
| E4BC8A | 伊   |
| E99BA8 | 雨   |
| E6A084 | 栄   |
| E5A5A5 | 奥   |
+--------+------+

業界によっては実は結構嬉しいのかもしれません。

MySQL 5.7が何も言わずに起動できなかったのでメモ

MySQL 5.6と同じ方法でMySQL 5.7を起動しようとしたら何も言わずに黙って終了してしまって少しだけハマったのでメモ。

MySQL 5.6では次のようにして起動してました。

# /usr/local/mysql-5.6/bin/mysqld --no-defaults --user=mysql
  --basedir=/usr/local/mysql-5.6 --skip-networking --socket=/tmp/mysql56.sock
  --log-error=/tmp/my56.err > /tmp/my56.err 2>&1

log-error をつけていても、最初の数行が標準エラー出力に出ちゃうので、log-error と同じファイルにリダイレクトするようにしていました。

同じようにしてMySQL 5.7を起動してみたら、すぐに終了してしまって、しかもエラーを何も出力しません。

# /usr/local/mysql-5.7/bin/mysqld --no-defaults --user=mysql
  --basedir=/usr/local/mysql-5.7 --skip-networking --socket=/tmp/mysql57.sock
  --log-error=/tmp/my57.err > /tmp/my57.err 2>&1

結論からいうと、エラー出力のリダイレクトが不要でした。5.6 と異なり、リダイレクトしなくても余計な出力がされることはありませんでした。

log-error と同じファイルにリダイレクトすることで、ファイルの所有者が root になり、mysql ユーザー権限でエラーファイルに書き込みできず、エラーを出力することもできなくて終了していたのでした。

5.6の時は log-error のファイルオープン時のユーザーはまだ mysql になる前の root だからオープンできたけど、5.7ではファイルオープン時に既に mysql ユーザーになっているからファイルのオープンに失敗しているってことだと思います。ソースを見て確認したわけではないですけど、たぶん。