昨日、Shibuya.rbがあったので参加して発表してきました。
内容は、ActiveRecordで"id"という主キーでないカラムを持っているテーブルにつなぐとおかしな挙動になったという経験をまとめたものです。
スライド公開後、 @mirakui さんからprimary_key=を設定してみては?というリプを頂いたので検証してみました。
@ryonext self.primary_key= で指定してもだめですかね http://t.co/AfDXsoWpXb
— Issei Naruta (@mirakui) 2013, 11月 20
# スライドで作ってたクラス class HyperLegacyTbl < ActiveRecord::Base establish_connection(:legacy) self.table_name = "hyper_legacy_tbl" end # primary_keyを"legacy_id"にしたクラス class HyperLegacyTbl2 < ActiveRecord::Base establish_connection(:legacy) self.table_name = "hyper_legacy_tbl" self.primary_key = :legacy_id end # primary_keyを"id"にしたクラス class HyperLegacyTbl3 < ActiveRecord::Base establish_connection(:legacy) self.table_name = "hyper_legacy_tbl" self.primary_key = :id end
1. legacy_idをprimary_keyに設定した場合
[21] pry(main)> h2 = HyperLegacyTbl2.new => #<HyperLegacyTbl2 legacy_id: 0, id: nil> [22] pry(main)> h2.id = 1000 => 1000 [23] pry(main)> h2 => #<HyperLegacyTbl2 legacy_id: 1000, id: nil> [24] pry(main)> h2.save (0.2ms) BEGIN SQL (0.2ms) INSERT INTO `hyper_legacy_tbl` (`legacy_id`) VALUES (1000) (0.1ms) COMMIT => true
こうなるのでスライドの内容と同じ挙動になりそう。
2. idをprimary_keyに設定した場合
DB上はプライマリキーじゃないけどRails上でprimary_keyとしてみる。
[36] pry(main)> h3 = HyperLegacyTbl3.new => #<HyperLegacyTbl3 legacy_id: 0, id: nil> [37] pry(main)> h3.id = 2000 => 2000 [38] pry(main)> h3 => #<HyperLegacyTbl3 legacy_id: 0, id: 2000> [39] pry(main)> h3.legacy_id = 1 => 1 [40] pry(main)> h3 => #<HyperLegacyTbl3 legacy_id: 1, id: 2000> [41] pry(main)> h3.save (0.2ms) BEGIN SQL (0.3ms) INSERT INTO `hyper_legacy_tbl` (`id`, `legacy_id`) VALUES (2000, 1) (0.1ms) COMMIT => true [42] pry(main)> h3.id => 2000
とりあえず#id
と#id=
はうまく動いてます。DB上の"id"はプライマリキーではないので重複がありえます。このときにRails上で正しく振る舞えるのかCRUD操作してみます。
C
[49] pry(main)> h3_next = HyperLegacyTbl3.new => #<HyperLegacyTbl3 legacy_id: 0, id: nil> [50] pry(main)> h3_next.legacy_id = 2 => 2 [51] pry(main)> h3_next.id = 2000 => 2000 [52] pry(main)> h3_next.save (0.2ms) BEGIN SQL (0.2ms) INSERT INTO `hyper_legacy_tbl` (`id`, `legacy_id`) VALUES (2000, 2) (0.1ms) COMMIT => true
うまく登録できますね。duplicate entryになったりはしない。
この時点でテーブルはこんな感じ
+-----------+------+ | legacy_id | id | +-----------+------+ | 1 | 2000 | | 2 | 2000 | +-----------+------+ 2 rows in set (0.00 sec)
R
ActiveRecordのデータ取得系メソッドを幾つか試してみます。
- find
[54] pry(main)> HyperLegacyTbl3.find(2000) HyperLegacyTbl3 Load (0.4ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`id` = 2000 LIMIT 1 => #<HyperLegacyTbl3 legacy_id: 1, id: 2000>
Railsでprimary_keyに設定した"id"で検索されます。id=2000で検索することになりますが、2件あっても1件しか返さないので結果は不定になるでしょう。find_by
と同じですね。
- find_by_id
同上
[55] pry(main)> HyperLegacyTbl3.find_by_id(2000) HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`id` = 2000 LIMIT 1 => #<HyperLegacyTbl3 legacy_id: 1, id: 2000>
- find_by_legacy_id
当然うまく動きます。
[60] pry(main)> HyperLegacyTbl3.find_by_legacy_id(2) HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`legacy_id` = 2 LIMIT 1 => #<HyperLegacyTbl3 legacy_id: 2, id: 2000>
- where
これも問題なし。
[61] pry(main)> HyperLegacyTbl3.where(legacy_id: 2) HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`legacy_id` = 2 => [#<HyperLegacyTbl3 legacy_id: 2, id: 2000>] [62] pry(main)> HyperLegacyTbl3.where(id: 2000) HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`id` = 2000 => [#<HyperLegacyTbl3 legacy_id: 1, id: 2000>, #<HyperLegacyTbl3 legacy_id: 2, id: 2000>]
U
更新処理。
[63] pry(main)> h3_1 = HyperLegacyTbl3.where(legacy_id: 1).first HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`legacy_id` = 1 ORDER BY `hyper_legacy_tbl`.`id` ASC LIMIT 1 => #<HyperLegacyTbl3 legacy_id: 1, id: 2000> [64] pry(main)> h3_1.update_attributes(id: 3000) (0.2ms) BEGIN SQL (0.4ms) UPDATE `hyper_legacy_tbl` SET `id` = 3000 WHERE `hyper_legacy_tbl`.`id` = 2000 (0.1ms) COMMIT => true
げえっ
mysql> select * from hyper_legacy_tbl; +-----------+------+ | legacy_id | id | +-----------+------+ | 1 | 3000 | | 2 | 3000 | +-----------+------+
レコードの一番目(legacy_id=1)のインスタンスがupdate_attributesするとidをキーに更新しに行くのでlegacy_id=2の方も更新されるので注意! update_attributes, saveともに同じ結果でした。
D
更新が上だったのでなんとなく想像つきますが
[86] pry(main)> h3 = HyperLegacyTbl3.where(legacy_id: 1).first HyperLegacyTbl3 Load (0.3ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`legacy_id` = 1 ORDER BY `hyper_legacy_tbl`.`id` ASC LIMIT 1 => #<HyperLegacyTbl3 legacy_id: 1, id: 3000> [87] pry(main)> h3.destroy (0.2ms) BEGIN SQL (0.3ms) DELETE FROM `hyper_legacy_tbl` WHERE `hyper_legacy_tbl`.`id` = 3000 (0.1ms) COMMIT => #<HyperLegacyTbl3 legacy_id: 1, id: 3000>
mysql> select * from hyper_legacy_tbl; Empty set (0.00 sec)
▂▅▇█▓▒░('ω')░▒▓█▇▅▂うわあああああああ
まとめ
残念ながら#primary_key=
をDB上の主キーに当てても#id
, #id=
が指すのは"legacy_id"のままで"id"の方は向いてくれない。
primary_keyとして"id"を割り当てると#id
, #id=
が使えるようになるものの、update,deleteの挙動が本来のActiveRecordのインスタンスの挙動と変わってくるのが厄介ですね。
追記:
この検証を行ったのは activerecord (4.0.1) です。 会社でこの問題で頭抱えた時はRails3.2.1x 系だったので3系でも当てはまると思います。
皆さんの発表
Railsにプルリク送ってみた話 by @sue445 さん
- First step of Rails Contribute #shibuyarb
- Rails4でlockの機能をサンプルコード通りに実装したらdepricateと言われたのでサンプルコードを修正した
- 英語で苦労したけどそんな怖い人達じゃなくて無事にマージされた。
プロセス監視ツール eye by @tyabe さん
- 「Eye」でカジュアルにプロセス監視
- godなどの後発なのでいい感じに踏襲している
- でもまだ不安定
vimでpull requestを見れるプラグインの話 by @joker1007 さん
- Vimでpull requestの差分を見るためのunite-pull-request作った(まだ途中) - Qiita [キータ]
- vimもGitHubも使っているので自分使ってみたら捗るんじゃないかなぁという気がした。
* ドキュメントツールを作った話 by @ryooopan さん
- GitHubからコードを取ってきて、コードを部分選択してメモを貼るWebサービスを作った話。すごい。
- このサイトCumiki
Twilio by by @joohounsong さん
- Twilioの中の人によるデモ
chromeのgitlab extension作った話 by @sue445 さんふたたび
- 社内でgitlab作っているのでそれを作った話とかJSのテスティングフレームワーク jasmineの活用とか
Refinements by @joker1007 さんふたたび
- Refinements Ruby2.0とRuby2.1での変化など
- 2.1からmodule scopeになるらしくて、活用できる箇所が増えそう。
最近の悩みが解決
- GitHub wikiを手軽にPDF化したいなぁと思ってて、それphantome.jsでできるのでは?という話が出て実際できそう。
- phantomjsのrasterize.jsがサイズ指定してpdf化できるので、これのスクリプト書いて実行すれば一気にGitHub wikiをpdf化、みたいなことがやれそうです。