かみぽわーる

kamipo's blog

プライマリキーを使った1:1関連のテーブル分割で自動採番をしないようにする

プライマリキーを使った1:1関連でカラム数の多いテーブルを分割する - Hidden in Plain Sight

プチ・デザインパターン的なやつ、僕もよくやってます。

で、運用エンジニア的にはデータの不整合を起こしうる要因はできる限りDB側の制約でも防ぎたいので、このusersとprofilesの場合だと、usersで自動採番されたidをprofilesでも使うのでprofilesの自動採番する機能は残しておくと事故るリスクがあるので落としたいわけです。

なので、僕はいつもこんな感じでmigrationを書いてます。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t| 
      t.string :email, charset: 'ascii', collation: 'ascii_bin', null: false
      t.string :password_digest, charset: 'ascii', collation: 'ascii_bin'
    end

    add_index :users, :email, name: "idx_email", unique: true
  end
end

class CreateProfiles < ActiveRecord::Migration                                                                                              
  def change                                                                                                                                
    create_table :profiles, id: false do |t|                                                                                                
      t.integer  :id, null: false
      t.string   :name
      t.integer  :gender, limit: 1                                                                                                          
      t.datetime :birthday                                                                                                                  
    end

    execute("ALTER TABLE `profiles` ADD PRIMARY KEY (`id`)")                                                                                
  end
end

usersテーブルの文字セットや照合順序の指定はRails - ActiveRecordでカラム毎にcharsetとcollationを指定する - Qiitaでやってます。

ActiveRecordが作る自動採番されるプライマリキーとは異なる定義を使うとrake db:schema:loadスキーマが正確に復元できなくなるので config/application.rbスキーマのフォーマットをSQLに変更しています。

config.active_record.schema_format = :sql

db/structure.sqlにこんな感じでダンプされます。

-- (snip)

--
-- Table structure for table `profiles`
--

DROP TABLE IF EXISTS `profiles`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `profiles` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `gender` tinyint(4) DEFAULT NULL,
  `birthday` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `schema_migrations`
--

DROP TABLE IF EXISTS `schema_migrations`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `schema_migrations` (
  `version` varchar(191) NOT NULL,
  UNIQUE KEY `unique_schema_migrations` (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `password_digest` varchar(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

-- (snip)

これでうっかりミスってprofilesにINSERTして採番されちゃうリスクが軽減されて安心感が増しますね!