[リファクタリングRails]更新頻度が低いカラムだけ別テーブルに抽出

# どんなときに有効?

- 1つのテーブルの中に更新/参照頻度が高いカラムと低いカラムが混在している
- 1つのmodelに大量のビジネスロジックがあって可読性が低い

# カラムを抽出するメリット

- memcache などキャッシュを使っている場合に無駄が減る
    - 参照頻度の高いデータだけキャッシュされるべき
- 更新頻度の多い情報のサイズは小さいほうが良い
    - DBのメモリになるべく乗るように

# 例

User modelの中に、更新/参照頻度が低い profile 関係の情報がある

- zipcode
- prefecture_code
- city_code
- address

# User model

```
$ rails g scaffold user name:string mail:string zipcode:string prefecture_code:integer city_code:integer address:string
```

```db/schema.rb
  create_table "users", force: :cascade do |t|
    t.string   "name"
    t.string   "mail"
    t.string   "zipcode"
    t.integer  "prefecture_code"
    t.integer  "city_code"
    t.string   "address"
    t.datetime "created_at",      null: false
    t.datetime "updated_at",      null: false
  end
```

# テーブルを分割する

## 1. Profile モデル作成

```
$ rails g model profile zipcode:string prefecture_code:integer city_code:integer address:string user_id:integer
```

## 2. Profile モデルと、Userモデルを関連付け

```profile.rb
class Profile < ActiveRecord::Base
  belongs_to :user
end
```

Profile model を delegate することで、既存コードの修正が不要に

```user.rb
class User < ActiveRecord::Base
  # delegateすることで、Profile モデルの中身を User から取得できる 
  delegate :zipcode, :prefecture_code, :city_code, :address, to: :profile
  has_one :profile
end
```

## 3. マイグレーションを作成


1. Profileテーブルを作る
1. Profileテーブルに移行する情報をUserモデルからコピー
1. Userテーブルから、Profileテーブルに移ったカラムを削除

```db/migrate/20160619032503_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration
  def up
    # Profileテーブルを作る
    create_table :profiles do |t|
      t.string :zipcode
      t.integer :prefecture_code
      t.integer :city_code
      t.string :address
      t.integer :user_id

      t.timestamps null: false
    end

    # Profileテーブルに移行する情報をUserモデルからコピー
    User.all.each do |user|
      Profile.create(
        user:            user,
        zipcode:         user.zipcode,
        prefecture_code: user.prefecture_code,
        city_code:       user.city_code,
        address:         user.address
      )
    end

    # Userテーブルから、Profileテーブルに移ったカラムを削除
    remove_column :users, :zipcode
    remove_column :users, :prefecture_code
    remove_column :users, :city_code
    remove_column :users, :address
  end

  def down
    add_column :users, :zipcode
    add_column :users, :prefecture_code
    add_column :users, :city_code
    add_column :users, :address

    # Profileモデルの情報をUserモデルに移行
    Profile.each do |profile|
      profile.user.update(
        zipcode:         profile.zipcode,
        prefecture_code: profile.prefecture_code,
        city_code:       profile.city_code,
        address:         profile.address
      )
    end

    drop_table :profiles
  end
end
```

# Profile情報が必要な時だけ、Profileテーブルを参照するようになった

```
irb(main):010:0> User.all.first.zipcode
  User Load (0.3ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
  Profile Load (0.1ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = ? LIMIT 1  [["user_id", 1]]
=> "419-4284"

irb(main):011:0> User.all.first.name
  User Load (0.2ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" ASC LIMIT 1
=> "木村 優衣"
```

# この後やるべきこと

- 必然性があまりないのに、Profileの情報を参照している部分を修正する

# code

- https://github.com/kasei-san/refactor_model

# 参考

- [プライマリキーを使った1:1関連でカラム数の多いテーブルを分割する - Hidden in Plain Sight](http://kenn.hatenablog.com/entry/2014/03/05/081525)
    - 必ず1対1であれば、わざわざ関連付けなくても、同じ値のプライマリキーを使うという方法
- [ソーシャルゲームのためのMySQL入門その２ - Technology of DeNA](http://engineer.dena.jp/2011/02/mysql-for-socialgame2.html)