背景
railsではrails g scaffold
などでModelを作成すれば、
自動的にidが付与されます。
しかもprimary_keyでauto_incrementでかつindexも張られるので、
普段はidを気にする必要はありません。
railsを使い、自分でデータ構造を決める場合はrailsの流儀に則った方が楽で、問題も起こりません。
しかし、古いデータを活用した場合、流儀にそぐわない事もあり得ます。
今回は規約に沿わない場合の対応について大きく分けて2つの場合について説明します。
なお環境は以下の物で検証しています。
- ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0]
- Rails 4.0.0
- composite_primary_keys (6.0.0)
主キーがidではない
主キーがidではない場合として、
具体的に商品(Item)テーブルの主キーが文字列である場合を考えます。
主キーがidで無い場合のポイントは
create_tabel
のid: false
です。
これによってidが自動生成されることはなくなります。
他のオプションでprimary_key
と言う物もありますが、
これはinteger型で名前をidから変更するだけの物です。
下記の例ではnull: false
とする事でnot nullな制約を追加し、
add_index :items, :code, unique: true
でユニーク制約を定義しています。
Migrationファイル
class CreateItems < ActiveRecord::Migration
def change
create_table :items, id: false do |t|
# 商品コード
t.string :code, limit: 8, null: false
# 商品名
t.string :name
end
add_index :items, :code, unique: true
end
end
モデル側にはself.primary_key
で主キーを設定します。
これによってfind
メソッドで主キーによる取得が可能になります。
Item Model
class Item < ActiveRecord::Base
# 主キー設定
self.primary_key = :code
end
複合主キーを扱いたい
次は複合主キーを扱う場合についてです。
具体的に先ほど使った商品(Item)について、
各商品に任意のタグ(Tag)がついているとします。
タグは商品コード(item_code)とタグコード(code)の複合主キーを持っています。
railsで複合主キーを扱う場合、
composite_primary_keysのgemを使用します。
composite_primary_keysの準備としてGemfile
にgem 'composite_primary_keys'
を追加しbundle install
します。
Migrationファイルのポイントは、
:item_code
と:code
の組み合わせがユニークである制約の定義です。
複合主キーがユニークである制約はadd_index :tags, [:item_code, :code], unique: true
だけでも十分です。
複合主キーが2つ3つと長くなる場合、インデックス名が長すぎてエラーが発生する事もあるので、
その場合はname
でインデックス名を指定してあげましょう。
Migrationファイル
class CreateTags < ActiveRecord::Migration
def change
create_table :tags, id: false do |t|
# 商品コード
t.string :item_code, limit: 8
# タグコード
t.string :code, limit: 8
# タグ名称
t.string :name
end
add_index :tags, [:item_code, :code], unique: true, name: 'composite_index'
end
end
モデルに関しては、
先ほどはself.primary_key
を使いましたが、
複合主キーの場合はself.primary_keys
を使います。
これによってTag.find 'item_code', 'code'
と、
findメソッドで複合主キーを扱うことが可能になります。
また、foreign_key
によって、どのカラムが外部キーなのかを指定することで、
関連を定義することが可能です。
Tag Model
class Tag < ActiveRecord::Base
# 主キー設定
self.primary_keys = :item_code, :code
# 商品との関連
belongs_to :item, foreign_key: :item_code
end
Item Model
class Item < ActiveRecord::Base
# 主キー設定
self.primary_key = :code
# タグとの関連
has_many :tags, foreign_key: :item_code
end
問題
composite_primary_keysによって複合主キーを取り扱うことが出来るようになりました。
しかし、少し問題が発生する場合もあります。
それはModelに対してto_json
を呼び出した場合などで、以下のエラーが発生します。
TypeError: ["item_code", "code"] is not a symbol
根本ではserializable_hash
を呼び出すところでのエラーなので、
モデルでserializable_hashをオーバーライドする事で解決します。
def serializable_hash(options={})
options = {
:only => [:item_code, :code, :name_code]
}.update(options)
super(options)
end