Posted at

Railsで規約に沿わない古いデータを扱う

More than 5 years have passed since last update.


背景

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_tabelid: 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の準備としてGemfilegem '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