composite_primary_keys
昨日は大ボケしてURLを間違えてた。トップは http://wiki.rubyonrails.com/ じゃなくて http://wiki.rubyonrails.org/ ですね。
寝不足だったせいか composite_primary_keys を使ってfindできるわーい、とか思って眠りについたため、朝になってCRUDのRead以外はどうなのよ?と気づく。テストケースを書いてみると・・・うわーん、だめだー。
生成されるINSERTやUPDATE、DELETEのWHERE句が文字列のプライマリキーを0と比較しちゃったりしてる。数値のキーならオッケーっぽいけど。
composite_primary_keysのサイトにもsaveのsの時も書いてないしな、まだ対応してないのかな。残念。
composite_primary_keys ちゃんとテストは通る
どうにも上手く行かなさ過ぎる。こんなにヘボイライブラリなら、http://groups.google.com/group/compositekeys のフォーラムに "Fix to update_without_callbacks" とかエントリがあるはずがない、と確信して、自分が何か間違っているとしか思えず、とりあえず
rake build_mysql_databases rake test_mysql
で試してみたら、49 targets, 172 assertions, 3 failures, 0 errors と出ました。failureは3つともDummyTestってやつで起きているので、多分無視していいのかなと判断。そんなわけで、やっぱり自分がおかしいことを確信しました。お騒がせして申し訳ないです。再チャレンジします。
composite_primary_keysは悪くないっぽい
なんで文字列のはずのカラムが、SQLのWHERE文で使われるときには、0という数値になってしまうのかを追っかけてましたが、composite_primary_keysの中ではどうもそんなことやってなさ気。
で初心に返って、実際のオブジェクトがどうなっているのかをチェックするために、モデルのcolumnsをinspectしてみたところ、PKのキーのtypeがなんと:integerで@number=true。そりゃ、文字列も0になるわってわけで、マイグレーションをチェック。でもおかしくなーい!
んじゃとりあえず、SQLで直接PKの型を変えてvarcharに。そしたら大体パスしました*1。
と言うわけで、PKにvarcharダメなんじゃねーの疑惑は(おそらく)晴れまして、次は:type => :stringというマイグレーションのカラム定義に対してintのカラムを生成するSQLを吐きやがったのはどいつだ、という犯人探しになってきました。
*1:destroyだけは何故か変なwhere文が吐かれて条件に引っ掛かってくれない
犯人はMysqlAdapter
というわけで何でそんな理不尽なことが起きるのかを突き止めました。ActiveRecord::ConnectionAdapters::MysqlAdapterのnative_database_typesメソッドの中身。
def native_database_types #:nodoc { :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "text" }, :integer => { :name => "int", :limit => 11 }, :float => { :name => "float" }, :datetime => { :name => "datetime" }, :timestamp => { :name => "datetime" }, :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => "tinyint", :limit => 1 } } end
という風になっているので、primary_keyに指定されたカラムは嫌でもint(11)になってしまいます。
ただし、これは、set_primary_key/set_primary_keysを使って複合キーでないキーを指定した場合の話です。varcharのカラムひとつをPKに使用とした場合とか。複合キーの場合は、まだ未確認だけど、昨日紹介したcomposite_migrationsが解決してくれちゃってるっぽいっす。
で、とりあえず回避する方法として、composite_migrationsプラグインのcomposite_migrations.rbを以下のように変更してみたら期待していたCREATE TABLE文が作成されるようになりました。
ActiveRecord::ConnectionAdapters::ColumnDefinition.class_eval <<-'EOF' def to_sql if name.is_a? Array column_sql = "PRIMARY KEY (#{name.join(',')})" elsif type == :primary_key column_sql = "PRIMARY KEY (#{name})" else column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}" add_column_options!(column_sql, :null => null, :default => default) end column_sql end EOF ActiveRecord::ConnectionAdapters::ColumnDefinition.send(:alias_method, :to_s, :to_sql) ActiveRecord::ConnectionAdapters::SchemaStatements.class_eval do def create_table_yield_before_pk(name, options = {}) table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(self) yield table_definition table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false if options[:force] drop_table(name) rescue nil end create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " create_sql << "#{name} (" create_sql << table_definition.to_sql create_sql << ") #{options[:options]}" execute create_sql end end ActiveRecord::ConnectionAdapters::SchemaStatements.send(:alias_method, :create_table_yield_after_pk, :create_table) ActiveRecord::ConnectionAdapters::SchemaStatements.send(:alias_method, :create_table, :create_table_yield_before_pk) ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do def primary_key_for_string(name) col = self[name] return primary_key_int(name) if col.nil? || name.is_a?(Array) || (col.type != :string) column = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(@base, name, :primary_key) @columns << column unless @columns.include? column self end end ActiveRecord::ConnectionAdapters::TableDefinition.send(:alias_method, :primary_key_int, :primary_key) ActiveRecord::ConnectionAdapters::TableDefinition.send(:alias_method, :primary_key, :primary_key_for_string)
コードが汚いけど、とりあえずこれでマイグレーションはOK。
でも何かおかしいんですよ。varchar1つだけのPKのモデルが。
結局、真犯人はだれ?
結局色々試してみて分かったことは、PKが1つのvarcharのカラムの場合だけどうしてもうまく動かないってことでした。
composite_primary_keysは複合キーを扱うためのものなんで、主キーが2つ以上のものを扱うためのものなんで、その守備範囲はきっちり守ってます。Railsはオートナンバー型のPKを期待している。ってことは、varchar一つのPKは誰の守備範囲なのさ?
うさうさ【右脳左脳占い】
http://www.nimaigai.com/howto.html
最初「ささ男」かと思って解説を読んでたら、何か結構当たっているけど絵がやな感じだったんでもう一回見直してみたら少しマシな絵の方でした。あー良かった。