has_secure_token を後から追加した時に token を埋める

# 結論

## 既存レコードのバリデーションが通る場合

```rb
User.where(token: nil).find_each(&:regenerate_token)
```

## 既存レコードのバリデーションが通らない場合

```rb
User.where(token: nil).find_each do |user|
  user.token = User.generate_unique_secure_token
  user.save(validate: false)
end
```

# 蛇足

## 背景

Ruby on Rails で動いていたプロジェクトがフロントだけ Next.js 等に移行することになって API サーバーとして動かすことになることってよくあるじゃないですか。で、安直にやるとRails に API トークンを発行させて認証っぽいことを実現したりすることってよくあるじゃないですか。

そんな時に Ruby on Rails 5 から追加された `has_secure_token` という素晴らしい ActiveRecord のメソッドがあって、データベースに `api_token` 列を追加しておけば、モデル側はこんな感じで1行追加するだけで勝手に新規作成されたユーザーに `api_token` を保存してくれます。

```rb
class User < ApplicationRecord
  has_secure_token :api_token
end
```

## `has_secure_token` コードリーディング

`has_secure_token` の実装は実にシンプルです。本体のコードを読みに行くことに抵抗感のある方は是非読みに行ってみてください。記事執筆時点ではたった数行のシンプルなコードです。
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/secure_token.rb

```rb
def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH)
  if length < MINIMUM_TOKEN_LENGTH
    raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
  end

  # Load securerandom only when has_secure_token is used.
  require "active_support/core_ext/securerandom"
  define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
  before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
end
```

肝はメソッド最後の2行です。軽く読んでみましょう。`has_secure_token` には `:api_token` を与えたものとして読んでいきます。引数を見て分かるようにデフォルトは `:token` です。

### `regenerate_api_token` メソッドの定義

さて、この `define_method` では `regenerate_api_token` のようなメソッドを新たに定義しています。メソッドの実装内容は、`.generate_unique_secure_token` なる `SecureRandom.base58` を返すメソッドでトークンを生成して、`update!` するというものです。

```rb
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
```

### `before_create` コールバックで値の代入

最後の行は、 `before_create` コールバックで先ほどと同様に `.generate_unique_secure_token` なる `SecureRandom.base58` を返すメソッドでトークンを生成して、 `api_token` に代入してくれています。

```rb
before_create { send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) unless send("#{attribute}?") }
```

とてもシンプルですね。

## 本題

さて、ようやくここからが本題ですが、前述の通り `has_secure_token` が `api_token` にセットしてくれるのは `before_create` コールバックなので、元々動いていたシステムで後から `has_seruce_token` をコールしても元々存在していたユーザーの `api_token` は空のままです。ちょっとしたスクリプトを流して全レコードの `api_token` を埋めることにしましょう。ここで先ほど `define_method` されていた `regenerate_api_token` を使うのが良さそうです。

```rb
User.where(api_token: nil).find_each(&:regenerate_api_token)
```

しかし、ここでお粗末なシステムを運用していたツケが回ってきます。`regenerate_api_token` は先ほど実装を読んだ通り `update!` を使って `api_token` を更新するので、バリデーションが通っていないレコードに対してコールすると例外を投げます。バリデーションが通らないようなレコードがシステムに存在しているのが問題なのは百も承知ですが現実は残酷なもので、今更データを綺麗にするような余裕はありません。バリデーション無効の状態でなんちゃって `regenerate_api_token` を実現しましょう。ここで、先ほど軽くコードリーディングしてきた経験が活きてきます。`regenerate_api_token` はトークンの生成に `self.class.generate_unique_secure_token` というメソッドを使っていることが分かったので、それに倣って一旦代入して `save(validate: false)` すれば ok です。

```rb
User.where(api_token: nil).find_each do |user|
  user.api_token = User.generate_unique_secure_token
  user.save(validate: false)
end
```

以上。