2014/01/26

rails+MySQLでdouble型カラムを扱うモンキーパッチ

背景

ruby2.1とrails4を使ってアプリを作っている。DBはMySQLを使っていて、わけあってあるテーブルの列をdouble型にしたいと思って。 カラムの列を変更するmigrationを作ろうと思った時に、そもそもdouble型のカラムを作るにはどうするんだっけ、ってなって、 Webを調べてみて、解がみつかったので試したところ、ちょっと不都合が生じて、それじゃあモンキーパッチを書いてみよう、という話です。

MySQLのdouble型を利用するmigration

まず、MySQLのdouble型の列を使うには、

add_column :table_name, :column, :float, limit: 53
のようなmigrationを書くとできる。(これは、既存のテーブルにカラムを追加するmigration)

型に:floatを指定して、:limitに53を指定する。参考にしたのは、

limit: 53は、MySQLのfloat型に精度を指定して定義する際に、精度が24〜53の場合は倍精度な浮動小数点型として扱われ、すなわちdouble型になるから、という風に理解している。(MySQL :: MySQL 5.6 Reference Manual :: 11.2.3 Floating-Point Types (Approximate Value) - FLOAT, DOUBLE)

問題点

上述のmigrationでカラムを追加すると、確かにdouble型のカラムになる。が、問題点が1つ。bundle exec rake db:migrateした時に、db/schema.rbが更新されるんだけど、このファイル内の当該カラムには、limit: 53が指定されない。結果、bundle exec rake db:test:prepareで、テスト環境のスキーマを更新した時に、当該カラムの型はfloatとして作成されてしまい、期待どおりにデータが扱えなくなってしまう。

ついでにいうと、本来double型を使いたいのに、:float, limit: 53って書くのもの、ちょっと違和感ありますね。

モンキーパッチ

db/schema.rbを手で直すってのも、あれなんで、なんとかならないものか、思って調べ始めたら、次のようなページを発見。

このページのハックを適用すると、
#add_column :table_name, :column, :float, limit: 53
# が↓のように書けるようになる。
add_column :table_name, :column, :double
となり、問題点の2つ目に対する解になりそう。が、db/schema.rbの問題は解決しないなぁ。もうちょっと調べるか、と思ってrailsのソースをみてたら、ActiveRecord::ConnectionAdapters::Columnというクラスのextract_limit(sql_type)というメソッドをみつけた。このメソッドは、どうやらSQLの型をみて、db/schema.rbを吐き出すときに:limitに何を指定するかを判断するものっぽい。各RDBMS用のアダプターでこれらをオーバーライドしているし。

ということで、次のようなモンキーパッチをconfig/initializers/add_double_to_activerecord.rbとして、作っておくことに。

module CustomColumnTypes 
  def double(*args)
    if args.last.is_a? Hash
      args.last[:limit] = 53
      args.last[:null]  = true
    end
    float *args
  end
end
module ColumnWithDouble
  def extract_limit(sql_type)
    case sql_type
    when /double/i
      53
    else
      $1.to_i if sql_type =~ /\((.*)\)/
    end
  end
end
module ActiveRecord
  module ConnectionAdapters
    class Table
      include CustomColumnTypes
    end
    class TableDefinition
      include CustomColumnTypes
    end
    class Column
      prepend ColumnWithDouble
    end
  end
end

これでdb/schema.rbにも無事にlimit: 53が出力されるようになった。

課題

上のパッチでは、ActiveRecord::ConnectionAdapters::Columnに対して振る舞いを変えているんだけど、僕がdoubleをサポートしたいのはMySQLの場合のだけだから、実はうまくない。なので、MySQL用のアダプタをイジる方法に変えるべきだなぁ、と思ってます。

0 件のコメント: