QA@IT

Railsでcollationを意識したMySQLのマイグレーションを書くには?

3541 PV

http://qa.atmarkit.co.jp/questions/2036

に関連して、なのですが、たとえばユーザテーブルに (email, remember_token)というカラムがあったとします。

この場合、emailは FOO@EXAMPLE.COMfoo@example.com が別人として登録可能だとまずいのでデフォルトの utf8_general_ci な状態でUNIQUE INDEX貼れば良いのですが、 remember_token で空間効率を優先するために Rand62 のような大文字小文字の区別を有効活用している場合、このカラムには utf8_bin を割り当ててやらないと、 012ABC012abc が同一視されて、UNIQUE違反になってしまいます。

これを回避するために

execute 'ALTER TABLE users MODIFY `remember_token` VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_bin'

などの生SQLをマイグレーションに書いているのですが、できれば

add_column :users, :remember_token, :string, limit: 32, collation: 'utf8_bin'
add_index :users, :remember_token, unique: true

のように書ければいいのになぁ、と思っています。

うまい方法ありませんか?

回答

ちょっと考えてみたんですが、パッチするくらいしか思いつきませんでした。
こういうのはどうでしょうか。

config/initializers/patch.rb

module ActiveRecord
  module ConnectionAdapters
    class ColumnDefinition
      attr_accessor :collation

      def to_sql_with_collation
        column_sql = __to_sql
        column_sql << " COLLATE #{collation}" unless collation.nil?
        column_sql
      end

      alias_method :__to_sql, :to_sql
      alias_method :to_sql, :to_sql_with_collation
    end

    class TableDefinition
      def column_with_collation(name, type, options = {})
        __column(name, type, options)
        column = self[name]
        column.collation = options[:collation]
        self
      end
      alias_method :__column, :column
      alias_method :column, :column_with_collation
    end
  end
end

db/migrate/untarakantara.rb

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :screen_name, :null => false, :uniq => true, :collation => 'utf8_bin'
    end

    add_index :users, [:screen_name]
  end

  def self.down
    drop_table :users
  end
end 
編集 履歴 (0)
  • ありがとうございます。collation定義はmysql固有の要件なので、パッチ書くなら `ActiveRecord::ConnectionAdapters:: ColumnDefinition` よりfirst/afterやbulk alterと同じく `ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter` に閉じ込めるのが良さそうです。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。