複数項目のユニーク制約の付け方、解除の仕方。

複数項目でのユニーク制約を付けたい場合、次のようにするのがよい。

class CreateTouhyous < ActiveRecord::Migration
  def self.up
    add_index "touhyous", ["touhyounin_id", "gian_id"], :unique => true
  end

  def self.down
    remove_index "touhyous", ["touhyounin_id", "gian_id"]
  end
end

* remove_indexの第二引数はインデックス名じゃなくて、索引対象となる項目名(またはその配列)なのね・・。

      • -

本来のSQL文であれば「ALTER TABLE `touhyous` ADD UNIQUE `touhyous_touhyounin_id_gian_id` (`touhyounin_id`,`gian_id`)"」になるはずだと思う。
そこで、最初、マイグレーションファイルで

execute "ALTER TABLE `touhyous` ADD UNIQUE `touhyous_touhyounin_id_gian_id` (`touhyounin_id`,`gian_id`)"

(↑)と書いて実行した。しかし、schema.rb を見たら、こんなの(↓)ができていた。

add_index "touhyous", ["touhyounin_id", "gian_id"], :name => "touhyous_touhyounin_id_gian_id", :unique => true
      • -

それはそれで、目的の機能を実現していることになる。
しかし、downメソッドでの書き方は、どうなんでしょ? 
こんな風(↓)に書いてみたら・・

  def self.down
    execute "ALTER TABLE `touhyous` DROP UNIQUE `touhyous_touhyounin_id_gian_id`"
  end

やはり、思った通り、バージョンダウンで失敗。

rake aborted!
Mysql::Error: #42000
You have an error in your SQL syntax; 
check the manual that corresponds to your MySQL server version for the right syntax to use 
near 'UNIQUE `touhyous_touhyounin_id_gian_id`' 
at line 1: ALTER TABLE `touhyous` DROP UNIQUE `touhyous_touhyounin_id_gian_id`

(See full trace by running task with --trace)
ここをクリックし、追跡を有効にしてタスクを再実行してください

まあ、これは MySQLの癖の問題なのだろう。しかし、

    remove_index "touhyous",  "touhyous_touhyounin_id_gian_id"

でも失敗。

rake aborted!
Mysql::Error: #42000
Can't DROP 'index_touhyous_on_touhyous_touhyounin_id_gian_id'; 
check that column/key exists: 
DROP INDEX `index_touhyous_on_touhyous_touhyounin_id_gian_id` ON touhyous

(See full trace by running task with --trace)
ここをクリックし、追跡を有効にしてタスクを再実行してください
-- remove_index("touhyous", "touhyous_touhyounin_id_gian_id")

念のため、作り方を変更して、次のようにして

    add_index "touhyous", ["touhyounin_id", "gian_id"], :name => "touhyous_touhyounin_id_gian_id", :unique => true

で作ってみる。しかし、出てきた schema.rb は当然同じ同じものなので、削除はやはり、失敗する。

そもそも、「DROP INDEX `index_touhyous_on_touhyous_touhyounin_id_gian_id`」って、名前が違うじゃん。バグかな? 

* テーブルごとDROPすればインデックスも消えるので、実害は無いのだが・・。

      • -

これ(↓)が正解のようだ。

class CreateTouhyous < ActiveRecord::Migration
  def self.up
    add_index "touhyous", ["touhyounin_id", "gian_id"], :unique => true
  end

  def self.down
    remove_index "touhyous",  :column => ["touhyounin_id", "gian_id"]
  end
end
      • -

投票用紙

バサッと作る。

class Touhyou < ActiveRecord::Base

  #
  #  議案モデルから投票モデルを作る
  # 投票人モデルをスキャンして投票人モデルidを取出す。
  #
  def self.create_by_gian(gian)
    gian_id   = gian.id
    soukai_id = gian.soukai.id
    sql =<<MMMM
insert into touhyous
(          gian_id , touhyounin_id  )
  select #{gian_id} ,id
  from touhyounins as t
  where t.soukai_id = #{soukai_id}
MMMM
#    logger.warn connection.class.to_s #=>ActiveRecord::ConnectionAdapters::MysqlAdapter
    connection.execute(sql) # , :soukai_id=>soukai_id, :gian_id=>gian_id)
  end

  #
  #  投票人モデルから投票モデルを作る
  #  議案モデルをスキャンして議案モデルidを取出す
  #
  def self.create_by_touhyounin(touhyounin)
    touhyounin_id   = touhyounin.id
    soukai_id       = touhyounin.soukai.id
    sql =<<MMMM
insert into touhyous
(        gian_id , touhyounin_id  )
  select      id , #{touhyounin_id}
  from gians as t
  where t.soukai_id = #{soukai_id}
MMMM
    #    logger.warn connection.class.to_s #=>ActiveRecord::ConnectionAdapters::MysqlAdapter
    connection.execute(sql)
  end
end
class GiansController < ApplicationController
  ・・・・
  def create
    @gian = Gian.new(params[:gian])
    respond_to do |format|
      if @gian.save
        Touhyou.create_by_gian(@gian)  # ←★
          #
        flash[:notice] = 'Gian was successfully created.'

バサッと消す。

class Touhyounin < ActiveRecord::Base
  belongs_to :soukai
  has_many   :touhyous , :dependent=>:delete_all  # ←★
class Gian < ActiveRecord::Base
  belongs_to :soukai
  has_many    :touhyou , :dependent => :delete_all  # ←★