activerecordでレガシーテーブルにつないだ話
DESCRIPTION
TRANSCRIPT
ActiveRecordでレガシーテーブルに繋いだらスタンド攻撃を受けた話
@ryonext
• タイトルは釣りです
自己紹介
• Twitter ID @ryonext
• ハンターランク151のモンハン厨です
• 新横浜にあるOA機器メーカーでビデオ会議のシステム作ってます
• RailsでAPIとかテスト書いたりとか
ActiveRecordは便利ですね 1 class CreateUsers < ActiveRecord::Migration! 2 def change! 3 create_table :users do |t|! 4 t.string :name! 5 t.integer :age! 6 t.string :type! 7 ! 8 t.timestamps! 9 end!10 end!11 end
14 ActiveRecord::Schema.define(version: 20131116064854) do!15 !16 create_table "users", force: true do |t|!17 t.string "name"!18 t.integer "age"!19 t.string "type"!20 t.datetime "created_at"!21 t.datetime "updated_at"!22 end!23 !24 end
mysql> desc users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | age | int(11) | YES | | NULL | | | type | varchar(255) | YES | | NULL | | | created_at | datetime | YES | | NULL | | | updated_at | datetime | YES | | NULL | | +------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
ActiveRecordの規約に沿っていないテーブル
mysql> desc legacy_user; +-------------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------------+--------------+------+-----+---------+-------+ | userID | int(11) | NO | PRI | 0 | | | name | varchar(255) | YES | | NULL | | | age | int(11) | YES | | NULL | | | someLegacyParams1 | varchar(255) | YES | | NULL | | | someLegacyParams2 | varchar(255) | YES | | NULL | | | someLegacyParams3 | varchar(255) | YES | | NULL | | +-------------------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
+--------------------+ | Tables_in_legacydb | +--------------------+ | legacy_user | +--------------------+
こんな感じでつなげます
14 legacy:!15 adapter: mysql2!16 database: legacyDB!17 username: root!18 password:!19 pool: 5!20 timeout: 5000
config/database.yml
1 class LegacyUser < ActiveRecord::Base!2 establish_connection(:legacy)!3 self.table_name = "legacy_user"!4 end
app/models/legacy_user.rb
ただし
こんなテーブルが合ったとする
mysql> desc hyper_legacy_tbl; +-----------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------+------+-----+---------+-------+ | legacy_id | int(11) | NO | PRI | 0 | | | id | int(11) | YES | | NULL | | +-----------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)
‘id’というカラムをもつが、主キーではなく普通のカラム
つないでみる
1 class HyperLegacyTbl < ActiveRecord::Base!2 establish_connection(:legacy)!3 self.table_name = "hyper_legacy_tbl"!4 end
繋がったようにみえる
[5] pry(main)> h = HyperLegacyTbl.new => #<HyperLegacyTbl legacy_id: 0, id: nil>
‘id’を更新してみる
[6] pry(main)> h.id = 100 => 100 [7] pry(main)> h.save
ファッ!?
(0.2ms) BEGIN SQL (0.3ms) INSERT INTO `hyper_legacy_tbl` (`legacy_id`) VALUES (100) (0.1ms) COMMIT => true [8] pry(main)> h => #<HyperLegacyTbl legacy_id: 100, id: nil>
なぜなのか
• “#id”によるアクセスはprimary_keyへのアクセスになっている
• “id”という名前のパラメータへのアクセスではない
対処:既存レコードの場合
[36] pry(main)> h = HyperLegacyTbl.first HyperLegacyTbl Load (0.4ms) SELECT `hyper_legacy_tbl`.* FROM `hyper_legacy_tbl` ORDER BY `hyper_legacy_tbl`.`legacy_id` ASC LIMIT 1 => #<HyperLegacyTbl legacy_id: 100, id: 200> [37] pry(main)> h.id => 100 [38] pry(main)> h.legacy_id => 100 [39] pry(main)> h.attributes["id"] => 200
取得には“#attributes[“id”]”を使う
対処:既存レコードの場合
[45] pry(main)> h.update_column("id", 1000) SQL (0.3ms) UPDATE `hyper_legacy_tbl` SET `hyper_legacy_tbl`.`id` = 1000 WHERE `hyper_legacy_tbl`.`legacy_id` = 100 => true
更新には“update_column”を使う
[46] pry(main)> h.update_column("id", 3000) SQL (0.3ms) UPDATE `hyper_legacy_tbl` SET `hyper_legacy_tbl`.`id` = 3000 WHERE `hyper_legacy_tbl`.`legacy_id` = 1000 => false
ただし、データを取りなおさないで2連続でやると失敗する
新規の場合
• Insertのときはupdate_columnが使えないので違う方法が必要?(未調査)
• 直接SQLを発行してしまえば
• それもうActiveRecordつかわなくていいよね説
どこで定義されているのか• #id
• lib/active_record/attribute_methods/primary_key.rb
• #define_method_attribute(attr_name)
• #id=
• lib/active_record/attribute_methods/write.rb
• #write_attribute(attr_name, value)
まとめ
• ‘id’っていう名前を持つテーブルにActiveRecordでつなぐときは注意しましょう